In this chapter, we're going to add a new trigger to our project. Google Form Trigger. And while we have already developed some useful nodes, which means that we will be able to reuse most of the code, this is the first trigger which uses a complete external service to activate some workflow within our application. So it will be quite challenging and interesting to develop this. Nevertheless, let's get started.
The first thing I want to do is I want to enable the user to add an option of a Google Form Trigger. So because of that I want to start with the node and the dialog for the Google Form Trigger. So basically the exact same thing that we have here for the manual trigger. I want this but for Google Form Trigger. Let's go ahead and get started by first downloading an asset.
So using the link on the screen you can visit my assets folder and go inside of images here and find googleform.svg and go ahead and add it in your app. So I'm going to add it inside of public logos right here. Googleform.svg perfect. Once we have that developed let's go ahead inside of our source, features, triggers, components And just as we have the manual trigger, I think that we can actually copy and paste this and just rename it Google Form Trigger. This way we can reuse most of the files inside.
Let's start with node.tsx. Let's go ahead and change the export to be Google Form Trigger like this. And Let's go ahead and change the export to be Google Form Trigger like this. And let's go ahead and change the node status here to just be initial. Just for now.
So yes, that will make all of these unused. That's fine. We're going to bring them back later. And then let's go ahead and just modify some things here. So we have a manual trigger dialog which we are going to change later to Google form trigger dialog but let's leave it like this for now and let's go ahead and modify the props of the base trigger node instead.
So for the icon, looks like it accepts both string and lucid icon, which should mean that we can now try forward slash logos forward slash Google form dot svg Because that is the exact thing we just added here. Google form.svg within the logos folder. Let's change the name here to be when form is submitted. Like that. And status will be status.
Yes, everything else will be the same. Okay. Now that we have the Google Form trigger, it's not enough for it just to exist here. We also need to go inside of node components node components is a file we maintain inside of source config folder and in order for react flow editor to render that we need to go ahead and add it here but one thing we're noticing is that it's missing within our node type so let's just go ahead and prepare this Google Form Trigger and let's go ahead and add Google Form Trigger. You can import it from Features, Triggers, Components, Google Form Trigger forward slash node.
Now in order to add this to our node type we have to revisit our schema prisma so instead of prisma schema dot prisma let's go ahead and add our new node type. So below HTTP request let's add Google form trigger. Once we do that let's go ahead inside of our terminal and let's do npx prisma Migrate dev. Let's give it a name of Google Form Trigger Node and once we submit that it should synchronize our database with our schema. What I suggest you do now is restart both of your ingest and next processes.
If you're using mprox like I am, you can highlight the process that you need and press the letter R on your keyboard. That's going to reset the process. Or you can just rerun npm run dev. So just make sure you've done that and refresh your app to make sure everything is still working. And now you should be able to go back to node components and node type should no longer give you an error, instead it should properly load Google Form Trigger.
Great! But that's not all we have to do. We now have to go inside of the Node Selector. If you don't remember, node selector is maintained inside of source, components, node selector. Node selector is the sidebar that opens up when we click on the plus button to add a new node.
So we have to add some new nodes here. Let's go ahead up here inside of the trigger nodes. Let's go ahead and duplicate the code for the manual trigger and let's go ahead and change it to Google form trigger. Let's go ahead and change the label to be Google form And let's quickly change the description. So this will be runs the flow.
When. A Google form is submitted. And for the icon let's change it to forward slash logos Google form dot SVG. And I think that already we should be able to see it here. There we go.
Google form. Now I'm not sure if, yes I think just by giving it the type that should be enough. Let's see if I click this. There we go. But it looks like something is wrong here or maybe it's not.
So when form is submitted. Yeah I'm not sure if if that's what it should be the title here. I mean depends if you like it you can leave it for this to be the title or you can go ahead inside of your newly created trigger features, triggers, Google form trigger, node.tsx. And in here, you can either give it a name here or you can use that as a description and give it a name of Google form. So if you prefer that, you can do it like this.
For some of you, I'm sure this might be a cleaner solution, especially if You later expect a Google form to have multiple ways of triggering the form. Like maybe when Google form is deleted or I don't know if it's updated, right? Things like that. So maybe this is a better option for you. The reason we are using the title for this type of trigger is because it's kind of the only thing that can happen.
So you can choose. Do you want to be consistent and just have the name here when the form is submitted? Or do you want to plan ahead maybe and change this to be a description and then give it a name of Google form whichever one you prefer Perfect. So one thing that's missing now is when I open this it opens the dialog of the manual trigger when it should open the dialog of the Google form trigger. So let's go ahead and fix that now.
So instead of our Google form trigger folder, let's go ahead and set up the dialog.tsx and let's slowly modify it So props will stay exactly the same but the name will now be Google Form Trigger Dialog. And let's go ahead immediately here, change this and add that. There we go. So nothing much has changed, we just renamed the component internally. But now let's go ahead and actually change what we need here.
So the user actually won't type anything inside of here. They will only be able to see the information they need to do inside of their Google Form to trigger this. So this will be Google Form Trigger Configuration. And the description will basically tell the user what they have to do. Use this webhook URL in your Google Forms app script to trigger this workflow when a form is submitted.
So this is how your description should look right now. And now we're basically going to give the user some way of doing this. So I'm going to go ahead inside of params. My apologies. I'm going to define the params constant using use params from next navigation.
So make sure you add this import. And then I'm going to define const workflow ID to be params.workflowID as string. So this will basically tell me what is the workflow ID that I'm currently in the editor of. Now let's construct the webhook URL. So const base URL will be process.environment next public app URL or let's go ahead and let's fall back to HTTP version of localhost 3000.
So next public app URL actually should exist here, but looks like it doesn't. So I think that we can just add it here. I'm going to add this on the other. And let's go ahead and define localhost 3000 here. Yes, so this doesn't make sense right now.
But think of production instances. So in production, we're going to change this to be the actual URL of our app when it's deployed, right, our dot-com domain. So I would rather that we always have that available rather than somehow using it in a different way. All right, now that we have the base URL, we can also do const webhook URL. The webhook URL will be constructed as following.
It's going to use the base URL, so either our .com domain or localhost, depending on if we are developing or something else, forward slash API, forward slash webhooks, forward slash Google dash form, with one single param workflow ID. And workflow ID will be appended here. There we go. So now that we have that, let's go ahead and define a simple copy to clipboard method. So this will be a super simple asynchronous method.
Here it is. It's asynchronous method. Open try and catch. In try, await navigator clipboard write text webhook URL, the constant we defined above, and then toast.success. In the catch, toast.error.
And make sure to import toast from sonar. So the reason I copied and pasted this is because it's super simple. It's just a simple copy to clipboard method. All right now let's go ahead and use this information above and actually display it here in the dialog. So instead of dialog header, let's go ahead and clear things up.
So let's change this to space Y4. Another div with a class name space y2 and let's go ahead and add a label we can import the label from components UI label and let's give this one webhook URL HTML4 webhook-url and then in here let's create a div with a class name flex and gap of 2 let's add an input from components UI input and a button from components UI button. The button will have a copy icon from Lucid React. And let's go ahead and give the copy icon a class name of size 4. The button itself will be a type of button so it doesn't actually accidentally trigger some submit form.
The size will be icon. Variant will be outline and onclick will be copy to clipboard. For the input itself we're going to set the ID to webhook URL, the same one we used HTML4 above. Value will be webhook URL. Read only will be true.
And class name will be font mono and text small. So, so far, when the user opens the Google Form trigger, they should see the webhook URL that they can copy. There we go, you can see it's right here. And they will be able to paste this inside of their Google Form Apps script. Now the problem is most users won't be able to do this on their own.
So we're going to make it a little bit easier for them by adding some instructions here. So after these two divs end, after the last button ends, open a new div here. Let's go ahead and give this div a rounded large background color of muted, padding of 4 and space Y of 2 let's give it an h4 element setup instructions let's go ahead and give the h4 element a class name of font medium and text small. Now let's open an unordered list with a class name text small, text muted, my apologies text muted foreground, space Y one list decimal list inside. And now let's go ahead and add all the steps that we needed to do.
The first step will be open your Google Form. The next step will be click on the three dots menu and then click on the scripts editor. I have no idea how to generate this arrow Unicode. You can just use this or just try and Google, you know, arrow Unicode and copy it. Third step will be copy and paste the script below.
Then replace the webhook URL with your webhook URL above which we are actually going to do for the user. This is first, second, third, fourth. Fifth step will be save and click triggers and then add trigger. And lastly, choose from form on form submit save. Again, you don't have to actually use this.
I mean, these are just instructions for your users. You will of course know very well how to do this. This is so your users know how to do it too. And if you're wondering, oh, well, Zapier has this, you know, one click implementation. You also have to be aware that these companies like Zapier and 8n, they most likely have some deals with Google and with other external services to make this much easier for everyone.
We are here completely trying to do this ourselves. So we have to use these methods. But I'm 99% sure that in the background of this one-click setups, It's actually the exact same thing happening. It's just all automated for those services mostly because they have a contact at Google and they all want to make it smoother. But this is what's actually happening in the background.
Don't take my word for it of course. That's what I think is happening and this is the best solution that I found. If you know a better solution, of course, feel free to write it down in the comments. I will be very happy to read and learn about it. Now let's go ahead and give the user an option to copy the Google Apps script.
So open a new div here and a class name rounded large background color of muted padding 4 and space y of 3 let's create a heading for Google apps script Give the heading for a class name font medium text small. And in here let's go ahead and let's create a button. The button will have a type of button, variant of outline, onclick, for now an empty function. And let's go ahead and add a copy icon here again, we already have it imported give it a class name size 4 mr of 2 copy Google apps script like this outside of the button create a paragraph This script includes your webhook URL and handles form submissions. Give this a class name, text extra small, text muted foreground.
All right. So now this doesn't make too much sense because we didn't actually create the on click to happen here. So let's quickly do that. So you can see what this Google Apps script will be. So this is a specific scripting language that I personally have no idea how to write.
I mostly did it with AI. So if you go back inside of my assets folder, you can find GoogleFormTriggerScript.ts. And this is basically the script, right? I just wrote it in this TypeScript form so that you can easily add it to your code. But this is the script.
I think this actually might be just JavaScript. I told you it's some random scripting language. It looks like it's just normal JavaScript. All right. But yeah, for example, what I meant to say is, I have no idea what name of the function should be.
I have no idea what this event has inside. So that's why I used AI help to basically do that for me, like this things, get item, get title. It's basically reading from the response and building a webhook payload. And then it's going to basically create a fetch request to our webhook URL. So let's go ahead and let's copy this entire script.
And now we're going to go ahead and create that. So instead of Google Form Trigger folder, create a new file utils.ts and let's just paste the entire thing inside. So export const generateGoogleFormsScript. It will basically copy this to user's clipboard and it will replace the webhook URL with the prop webhook URL. Let's go ahead and add it to our dialog.
So I'm just going to go ahead and make this an asynchronous method. Const script will be generate Google Forms script as in the webhook URL constant which we generate above. Make sure you've imported our generate Google Form Script from .slash utils. Open try and do await navigator.clipboard.writeTextScript and do toast.success.script copied to clipboard. Clipboard.writeTextScript and do toast.success.script.copiedToClipboard.
And in the catch, let's go ahead and just throw a toast.error, failed to copy script to clipboard. All right. And, okay, I think this is good enough. Let me go ahead and just try this now. So just make sure you have your app running somewhere.
Let me go ahead and add my Google Form trigger here. And when I click Copy Google Apps Script, it says Script copied to clipboard. And take a close look at my webhook URL. So if done correctly, it should now add this entire webhook URL with my Google Apps script. So I can very easily check if that is true.
There we go. Function onFormSubmit has a webhook URL which is exactly what I expected it to be. Amazing. So, our generate Google Forms script works. And now, what you can do here is just give your users some more information.
So, after this div, after the last paragraph here, open a new div with a class name, rounded, large, background color muted, padding 4, space Y of 2, add an H4, available Variables. So just some things to help your users, right? Font, medium, text, small. You will of course see more of these valuables which are available in the InGIS developer screen. So let's make an unordered list here with a class name text small text muted foreground space y one.
And let's go ahead and add a list here. And let's add code. And for example, one of the things users will be able to do is access Google form dot respondent email. And let's give this a class name BG background BX of one PY point five and around it. And you can explain what that is.
Respondent email. And then you can copy this list, paste it. And you can, for example, show the user how they can access the question name, like this. For example, this would be a specific answer. And then you can do the same thing if you want to list all responses as JSON using our registered JSON helper.
So basically just the way you can see I have to zoom out a bit. Just the way to help your users so they know how to access this in the next node that will be connecting to it. If you want, if you don't want to, you don't have to add this hint for your users. So now that we have the Google form ready, I mean, we just have the UI ready, we still have to create the actual webhook that will listen to it. And besides the actual webhook, we also need the executor.
Let's actually do the executor first and I'll show you why. So inside of features, triggers, Google Form Trigger, we also have the Executor.ts. And it's basically just this. That's it. So let's change this to be Google Form Trigger Executor.
Change this from manual trigger data to Google form trigger data. And that's it. Just change the step run to be Google form trigger. And since we are here, you can obviously notice that we are using manual trigger channel to publish the loading and success states. So yes, the Google form trigger itself will not start anything Because this will not be activated on click like our manual one is.
We just click execute workflow. Google Form Trigger can only be submitted through a webhook. So that's why the executor is so simple because it only needs to tell the user, all right, I received an event. That's it. So while we are here, let's go ahead and let's quickly create the Google Form Trigger channel so that we can change it.
So instead of Google form trigger. Oh yes, we don't do them there for some reason. I have to improve that. We do them in ingest channels. Well in one way they are all in one place so at least that's good.
So let's copy manual trigger, paste it here, rename it to google-form-trigger.ts. Go ahead and change this to Google form trigger. Change this to be Google form trigger. And change this to be Google form trigger and change this to be Google form trigger channel. Basically no instance of manual just Google form.
Everything else is exactly the same. Once we have this channel we have to go inside of functions.ts inside of source ingest right here and we have to register our new channel so make sure you import the new channel. Perfect. Now once we have that we can go back inside of the executor.ts inside of our Google form trigger folder and we can now finally replace all instances of manual trigger channel with Google form trigger channel. And we also have to fix the import now.
Google form trigger. There we go. Now that we have that working, Let's also go inside of actions.ts. So instead of manual trigger token, let's rename this to Google form trigger token. Let's rename the function to fetch Google form trigger real-time token and let's go ahead and replace the channel instances to our Google form trigger channel.
Let's go ahead and fix the import Google form trigger and there we go. Now we have actions.ts which will register to Google form trigger channel. Now that we have that we can go back instead of node.tsx and we can finally revert this. So let's take a peek at how it's done instead of the manual trigger. Let's go ahead and borrow the code here.
Here it is. So I'm just going to copy it. I'm going to go back instead of my Google Form Trigger node.dsx and I'm just going to paste the entire thing here. Let's go ahead and change this to be Google form trigger channel name and fetch Google form trigger real-time token. Let's go ahead and remove the unused import from the actions.
Let's go ahead and remove the unused import from manual trigger. Let's go ahead and remove the unused mouse pointer icon. And I think everything else should now be fully used in our code. Let me just double check that inside of my executor I'm using Google form trigger. Perfect.
If you want to do a final check, you can go ahead and highlight your Google form trigger folder and click find in folder and search for manual, manual trigger. Perfect. If nothing shows up, you don't have any leftovers because we copied this from the manual folder. Great. So we are very far ahead with our Google form trigger.
One issue though is that we never actually developed that webhook route. Which webhook route am I talking about? Well, this one. My apologies. This one.
This endpoint right here doesn't exist. So even if we copy the Google Apps script and create a new form and paste it here it would just return a 404 because our project has no idea what route that is So let's go inside of Source App folder, API folder In here, let's create workflows Inside of workflows, let's go ahead and create Google form. And inside of here let's create route.ts. Route is a reserved file... Oh, you already know that, right?
We went through this, my apologies. Yes, it's a reserved file name, just like page.tsx. So let's go ahead and just add some imports here. So let's import type next request and next response. Let's export asynchronous function post.
Let me fix a typo in the function. Let's go ahead and make the request a type of next request. Let's open a try and catch right here. Let's go ahead and resolve the catch since it's easier. Console.error Google form webhook error and simply log the error.
It's always good to do these things even though we have Sentry. So you will have advanced error logging so it will be way easier for you to discover if anything like this breaks. That's why it's super useful to have something like Sentry here because there's so many factors that can go wrong. It can be user input, it can be your implementation, maybe just the way Google Apps Script works can change. A bunch of things can happen.
That's why having Sentry so they log errors for you and you don't have to log them is a big, big help. So let's also officially return a response with success, false, error, failed to process Google form submission. And let's also pass in the status, 500. Now inside of the actual post method, what we have to do is we have to destructure the URL. So new URL, request.URL, and then we have to get the workflow ID from the params.
So URL.searchparams.get workflow ID. In case we are unable to find workflow ID, it means we have no idea what background job to trigger. So let me go ahead and copy this right here and let's go ahead and simply say instead of failed, missing required query parameter workflow id and in this case it's most likely a user error so 400 instead of 500 now if we do have a workflow id we can go ahead and destructure the body using await request.json and then we can submit form data now instead of the form data we can add everything relevant to the form for example form id, Form title and all the other things that we might need But I also suggest that whatever you choose to do like I do right here individual ones You can also always pass the raw body. This will allow your users to do all kinds of things Basically using our template language they will be able to access the raw object and then they will be able to access whatever they specifically want from the Google form. But the reason we did this even though raw exists is just to save the user some time and to make the templating simpler.
And now what we finally have to do is trigger an ingest job. But let's go ahead and take a look at how we currently do that. So I think that we have workflows routers.ts in the server folder. So instead of source features workflows server routers.ts we have the execute folder right here and in here we do await ingest dot send like this. So we could do that right here.
This would work perfectly fine. But here's what I want to do instead. I would suggest that we wrap our ingest.send into our own abstraction so that later if we need to modify it for whatever reason We can easily do it in just one place instead of all the other places which will call this function. So instead of ingest, go inside of utils.ts since we already have it. Let's reuse it once again.
At the bottom here, export const send workflow execution. Let's go ahead and make this asynchronous and make the data be workflow ID which is required and then anything else we might want to pass Go ahead and return ingest.send name workflows forward slash execute dot workflow and pass in the data. Let's go ahead and let's import ingest from .slash client. So just double check that we didn't accidentally misspell this. And yes, now we are only gonna have to spell this properly once.
We don't have to worry if we misspelled it here. So now what we can do here is we can completely change the way we call this. So instead of doing this, we can do await sendWorkflowExecution here and pass in the workflow ID to be input.id. And then you no longer have to pass this. And then same thing here.
You can just pass send workflow execution. And instead of input.id, you actually have the workflow ID so you can just use the shorthand operator. But here's the thing. You actually don't want to just pass that now. In our previous examples, we always began with an empty context until something happened like an HTTP request.
But this time it's different. This time we will start this background job with some context. So let's pass in the initial data here, Google form form data. Whoops, did they call it form data? I did.
So let's just pass it as FormData here. And then let's go ahead and quickly revisit how our functions.ts work. Instead of source, ingest, functions.ts, we can extract the workflow ID. And what we do here is basically we just initialize, oh looks like we already do it, perfect. So yes, our context will either use event.data.initialData or it will use an empty object.
So far we've always had an empty object. Why? Well because we manually started the workflow. So obviously no initial data could have happened from our manual click. But now it's a different situation.
Now we have this Google Form which will parse all of this data from the Google Form submission and it will then start the ingest job using initial data. So that will be a completely different situation now. Perfect. So now that we have all of this ready, let's go ahead and actually try it out. So I'm going to simplify this just a little bit.
Let me remove this one. Let me remove all of them. And I'm going to add Google Form. And then I'm going to add, well, yeah, let's let's do just for fun. Let's do two of these.
This will be, I don't know, my API call, HTTPS code with Antonio.com. Or you can use the pretty variables. Let me just remember which one it was. This one. So you have nice JSON.
So user, there we go. Save. Save here. As you can see now, immediately we have no execute button. So the only thing we can actually do here is we can copy the Google Apps script and we can paste that instead of Google Form.
But here's the thing. This still won't work. Here's why. So this is the exact script that will be pasted inside of Google Apps script. The webhook URL is localhost 3000.
That will not work. External services have no idea what localhost is. Localhost is only available on your current device. So in order to resolve this, we have to add ngrok or any other local tunnel. I highly suggest ngrok because it is super reliable and it also offers you one static domain which will basically always be the same no matter how many times you start ngrok.
So I'm gonna quickly show you how you can set that up. So head to ngrok.com or use the link on the screen. And once you create an account, you will be greeted with a welcome screen like this. In here, make sure you are looking at the agents drop down here and select your agent, basically your operating system. If you are on Windows, select Windows.
Do not confuse that with SDKs. This is something different. This is if you want to use it programmatically. That's not what we are looking for. We want to use it as an agent.
So in my case I select macOS and I can either download it or I can use homebrew to install it. And once you've done that, So you have two steps to run brew install ngrok and then you have to add the auth token. Do not share this token with everyone. So I show it for tutorial purposes. I will rotate this token after that.
So it's a new one. And to test if you did it correctly, you should have ngrok available inside of your terminal. So if I run ngrok, you will see that it now works. And if I go ahead and try to ngrok HTTP 3000, what it's going to do is it's going to capture my next JS instance, which is running at all closed 3000. And it's going to forward that to this public endpoint.
But you can see that it's completely random. And every time that you try and you know, use it, it's going to be completely random as well. But it works. You can see that my app is available through that weird URL. But yeah, the problem is every time you try and do this, it's completely random.
So this old one now no longer works. There is a way you can actually fix that completely for free by going inside your sidebar and let me just remember here it is, Universal Gateway Domains. So click on the domains here and if you don't have any, I think on the free tier you can just create a new domain and then basically in here you will always have one completely free domain and you can click on the little CLI button here and it's going to show you how you can select it. So now you can see that I have this thing and now I can always do this and it's always going to be this domain. So a custom domain for my free account.
And then I can use that here. I think this is very useful for development because you can see every time I run this, it's always the same domain. So you don't have to change your code that often. So, okay, Let me just close this. If you're wondering what was that file, it's just the Google Apps script that I copied and then pasted here to demonstrate what's actually being copied to the clipboard.
So if you're using mprox, what I would suggest you do is you... Well, let's go ahead and do it together. Let's go inside of package.json first. And inside of package.json, let's go ahead and do ng-rock-dev. And in here, Let's do, I already forgot the script, this one.
There we go, like this. So then, When you are inside of your project, you can just do npm run ngrok dev. And then you will always have your local tunnel running so you can test your webhooks. But I'm going to go ahead even further. So what if you want this to be dynamic?
Well, that's completely reasonable. So I'm going to go ahead inside of my dot environment here. And in the other, I'm going to add my ngrok URL. Keep in mind, all of this is completely optional at this point, right? I mean I showed you how you can do it yourself using ngrok so if you manage to have your app running using this, perfectly fine.
You can continue with the tutorial. So if this part doesn't work for you because I know mprox can be a little bit tricky for Windows users, that's perfectly fine. What I'm doing here is just making it more convenient. Just doing it so it's a better team environment, right? So yes, it kind of would be reasonable to store that instead of your .environment so then in your package.json you don't have to literally use the URL and instead what you can do is you can reference the environment variable using $angrok URL.
But that will not work just like that. You can see now it's not working. It falls back to random URLs. The reason it doesn't work is because it's missing .environment.cli. So let's do npm install...
Whoops... Npm install .environment-cli and save it as a dev dependency because you don't actually need it in the dependencies so let me show you that .environment-cli, I'm using version 10.0.0 And once you have that, you can add a prefix here, .environment. And then try it again. Npm run ngrok dev. Looks like it is still not working.
Because yeah, my apologies, you will have to run it like this so add two dashes here let's go ahead and try again this hopefully this time it will work again not working okay dot environment ngrok ngrok url Let me check if I'm doing something incorrectly. Ngrok URL it's right here. Ngrok URL right here. Maybe that's simply not the way it can work. So instead, what I'm going to do is the following.
I will do what worked for me. I will put .environment here in my mprox like this. And then I'm going to go inside of my mprox.configuration file and I'm gonna go ahead and add a new process here called ngrok.cmd.npm run ngrok dev And I think that now it should work hopefully because this is what worked for me. So I had dot environment here and I also had it here and in mprox. So I'm going to go ahead and shut this down.
I'm going to shut this down and npm run dev all and now I should have three of them running and now it's working. So you can see now I have my ngrok running at my static URL which I can now always easily find here so I never have to guess what it is. I can just paste it in my URL here and I have my app running. So sorry for detouring so much. I just really wanted you to have that because it's useful to develop like that.
Just having one command being run and all configured from your dot environment file. But again, You can completely, you know, just do what we previously had. This, this is perfectly fine, right? I'm just thinking that some of you might be doing this in a company or maybe you want to impress your employers. So yeah, it would be kind of useful to do it like this so they can easily set it up for their webhook URL.
All right. So now that we have that ready, make sure that you have your ngrok running on any URL. It really doesn't matter, just you should know what the URL is. And then we are now ready to do this because if we try to access our webhook URL through our new forwarded URL, we can now do it. So let's go ahead and create a Google form.
So here I am in the Google form tab and I'm going to click start a new form. I'm going to call this node base, no thanks, node base test. And for the question here let's do what endpoint should I fetch? And let's go ahead and make this... Can I make this like a test text based answer?
I have no idea. I'm not that good. OK. So here. Yeah.
I just want like a short answer like a URL. I think this will be fun. Make it required. And let's go ahead and publish this. Let's click publish.
Okay. And now I should be able to copy responder link. I should be able to copy it and in my new tab I'm going to paste it. And there we go. So we are having this super simple question.
What endpoints should I fetch? And now in here, users would, for example, answer something like this, right? Our JSON placeholder type the code. So the goal is so that we create the following thing. When Google form is submitted, read the user's answer, and then we're gonna use a variable here instead.
That's kind of my idea behind it. Now, obviously, if you just submit, nothing will happen because we didn't add any script. So I haven't done too many of these scripts and this is all new to me so you're gonna have to bear with me. I'm not the expert at Google Forms so I will kind of try to follow my own guide here, maybe get stuck a few times but we will get through this. Let's go ahead and do it together.
All right, so first things first, open your Google Form, click on three dots, menu and script editor. Apps Script, okay. That's it. So we should rename that to Apps Script. And once we open that, it should load the Apps Script editor for this specific form.
Looks like it's taking a while so I'm just going to pause the video until it loads. All right, here it is. So let me zoom in. I will call this project the NodeBase Google Apps Script. And this is it.
This is the function. So you can see it's not JavaScript. It's, I'm guessing, GoogleScript.js. I'm not sure. And now in here I can copy Google Apps script and I should be able to paste it like this and now basically what we have to change is this it shouldn't be localhost 3000 It should be our running URL right here.
So since I have it inside of my dot environment, I'm just going to copy it from here and make sure you are using the HTTPS option. So change this to HTTPS and then paste it here. Make sure you don't add any double slashes. So HTTPS and then your URL link. Because we are using HTTPS right here.
In production, obviously, you wouldn't have to change this because it would be the correct domain. In development, it's localhost, so it's a little bit harder to do. And I think that this saves automatically. Oh, you can do command save and then it will save the driver, you can just click here. Okay, so that works.
And now let's go ahead and go inside of triggers here. I think that's the next step. Yes, let's go ahead inside of triggers And click create a new trigger, add a trigger. There we go. Choose which function to run.
So on form submit. You should have this because you just saved the file which has that function. And let's go ahead and see what else. Choose from form on form submit. From form on form submit.
Perfect. And let's click save right here. And let's see what will happen. All right, script authorization failed. Please check your pop-up blocker settings and try again.
So I'm going to click here and I'm going to allow pop-ups and redirects from this website. And I think I know exactly what's happening here. So I have to re-verify my account now. And this is very important. What you're seeing right here is warning you specifically.
So when I first saw this message, I thought, oh, this is a bad implementation because, you know, my users will see this and they're gonna have to, you know, see this message, that's not nice. This is only warning the script creator, the form owner, right? Why is it warning us? Well because these scripts can be dangerous, right? Who knows what we just copied here and pasted.
Imagine someone who is not as technically capable as you are. They can very easily hide some malicious code here and tell them, yeah, just paste it here. So that's why Google is telling you that this is requesting access to sensitive info. Because it is. We are accessing the Google Forms submission data and we are sending that to some random endpoint.
That is what Google is warning us. But obviously you can also get rid of this warning too once you verify this app with Google. So those are production steps. So this is 100% safe. You can click go to NodeBase Google Apps script even though it says unsafe.
I know this sounds very weird But that's because look at what it will do. It will view and manage your forms in Google Drive and it will connect to an external service. But we are the ones doing that, right? So that's why you can allow this to happen. I know it sounds sketchy but it's perfectly fine, right?
You developed this. You saw the exact script that is pasted inside. You know exactly what it does. And now it says loading data. This may take a few moments.
I think at this point you might even be able to submit or maybe you just have to create a new trigger again. I know this part was a bit weird for me too. Here we go. Owned by me, deployment head. To be honest, I have no idea what these deployments really mean because I think that all I can do is I can just always just save the file and it will work.
I have no idea what deployments mean really. I'm not too familiar with Apps Script. I just managed to get it work this way. Perfect. So we now have this.
Make sure it's onFormSubmit from form onFormSubmitEventType and make sure that the code is exactly that you have copied from here, right? And the only thing you should have modified is the webhook URL to use your active ngrok tunnel. And I think that this should be it. Keep in mind that this will obviously only work for... Whoops...
For this specific endpoint. So let's go ahead and try it out. I have no idea what we can expect actually. So let me just see what this error is. Some hydration error.
I'm not too sure what that is. I will focus on that later. For now, let's submit a form. So I will just copy the link. I will paste it in one of my tabs.
And yeah, let me go ahead and try and add some useful URL here. But let's actually try with this one. It's more recognizable. And let's click submit. Let's wait here.
Maybe we can already see it being submitted here. Maybe we won't. I have no idea. Looks like it is not being submitted right here. Let's go ahead and check it here.
Looks like no results are found here. This would mean that it is not working I believe. So let's go ahead inside of the triggers here. Maybe it will take some time for the first one, I don't know. I'm not sure.
Error rate is a 0%. That's good. Let's go ahead and click on executions here. Looks like something did trigger it and it did manage to complete. If I click view trigger it just redirects me here.
Still I cannot see any runs happening here, nor here. Let me try my ingest here. Can I maybe see a hit somewhere here? Oh, here it is. We have it.
API, webhooks, Google Form. It's 404. Could I have maybe made some typing mistake here. Source app folder API. Did I name it workflows?
I named this workflows instead of webhooks. My deepest apologies. Select yes to update imports. That's going to open this cache file. You can just save it, close it, close the .next folder.
That was the problem. I think that now it should work. Let's try again. It's super easy to retry. Just click submit another response.
Let's go ahead and try code with Antonio.com again. Let's click submit. And will it maybe work now? Oh, there we go. It failed, but no executor found for node GoogleFormTrigger.
Alright, this is actually good news even though it doesn't sound like it. We forgot to add our executor. But it's good because something tried to trigger the executor. So instead of our triggers, we have Google Form Trigger and we have executor.ts. But yeah, we never actually use this.
If you search, we don't use it absolutely anywhere in our code except in its definition. So let's go ahead and I think this is in executions. Maybe not. Let me go ahead and try and find. Okay, I think I know the name of the file.
All right, it is in source features, Executions, lib, executor, registry. Yes, we have this to do that we have to do. I will take care of that. But let's add node type dot Google form trigger and let's use Google Form Trigger Executor. Make sure you have imported it.
And I think that now, third try, I think this should work fine. Let's refresh for good luck. Oh, there we go. It retried itself and then it worked. That's great but let's try ourselves again from scratch.
So I'm going to go ahead and click submit another response and I'm going to do https://codewithantonio.com, submit, let's wait, let's wait. It should highlight any second now. Let's see. There we go! Finally, it works!
We successfully triggered using a third party service. And let's go ahead and take a look. Instead of our Google form trigger, we have Google form variable. We have the form ID, form title, we have raw data, We have respondent, well we don't have respondent email because we didn't make it required in the Google form. But we do have responses.
What endpoints should I fetch? Like this. So if I am correct, The way I could now do this is by using Google Form dot, let me see, dot responses and then quoting this. And then, quoting this. I think, if this doesn't work, it's probably because of the white space, so I should rename the question to be a single word but let's try it.
Let's go ahead and click save here. Okay, I see. Let's go inside of HTTP request dialog.tsx and let's go inside of the form schema here. Yeah, I think It's complicated, yeah. Let's make it a string and let's go ahead and chain this to be at least required.
Like this. So Yeah, endpoint will be any string. And I think that now this save should work. And let's click save. And we can refetch now.
So now, you can see that Basically, when the form is submitted, we're going to read from the context, googleform.responses and specifically that question. And now let's go ahead and try and make it more fun. So I'm going to go ahead and submit my form again. So submit another response. What endpoint should I fetch?
I'm going to use HTTPS JSON placeholder users one. And I will click submit. I have no idea if this is going to work or not because of the white space in the question name. So let's see. Yeah, it does not work.
I'm guessing because of that specifically. Let's see. Yes, it cannot do that. So can I make it simpler by calling this URL? Save.
Submit another response. Now it's called URL. So I think that I can just modify this to be, oh, I think it should be just .url now because it's just the name of that question. So let me save that. I'm going to do another refresh here.
I mean, at this point, what we wanted to implement for this chapter is finished. I'm just trying to make some fun conclusion before we wrap up the chapter. So let's go ahead. Fingers crossed. There we go.
Perfect. So if we've done this correctly, HTTP request should fetch the first user and that's exactly what it did. It fetched the user with an ID of 1 because that is exactly what we submitted in the Google form which we can prove right here. Respondent responses answered the URL should be users one. Amazing, amazing job.
You just implemented a third party service trigger to your app. What an amazing job you've done. You've learned so many different things in this one chapter. And yes I completely forgot about this. I will make sure that we resolve that.
So let's go ahead and let's finally merge this thing now shall we. So 22 Google form trigger. I'm going to go ahead and open a new branch here. 22 Google form trigger. There we go.
I'm going to go ahead and stage all 20 of my files here, including the new mprox. If you didn't change that you might have 19, 18 files. I don't know. But yes, these ones should be the important ones. Let's go ahead and do 22, Google Form Trigger, commit, and let's go ahead and publish the branch.
Now as always let's go ahead and open a new pull request and since this was a big one let's go ahead and review it. And here we have the review by CodeRabbit. So new features. We added Google Form Trigger support. Workflows can now be triggered by Google Form submissions with webhook configuration and real-time status monitoring.
Enhancements. We added ngrok integration to development environment for local webhook testing. Improvements. Relaxed endpoint validation to accept non-URL string inputs. This refers to our last change where we basically allowed the entire endpoint URL field to be a variable.
Otherwise, it would not work. So let's take a look at the sequence diagram, even though I think it is pretty simple. So Google Form makes a POST request to forward slash API webhooks Google form with required workflow ID and form data. We then proceed that information to send workflow execution which fires the ingest background job. We then execute the Google form trigger which is very simply used to publish the loading status and forward the context to whatever is the next topologically sorted node.
As per some comments in here, it says that this script assumes authentication is already configured but this prerequisite is undocumented. So yes, all of this is true. If your users, if you will, you know, give this source code to someone, you should probably tell them that ngrok is required and that they need to have ngrok set up. Another thing it mentions here is that ngrok URL parameter suggests a reserved domain configuration. That is not ngrok pro feature.
So yes, multiple domains are, but single domain is not. So yes, it's fine to have this on free tier. In here it is warning us that technically anything can access this webhook right now, which is completely true. So I'm going to see if there is a simple way I can show you how to authorize your webhooks. But if you're interested, there is this service called Svicks webhooks, which I know Clark uses to protect their endpoints.
So it could be something you could explore for production use, basically WebCooks as a service. For now, let's just focus on this. So yes, right now anyone could access this. I will try to implement something simple, so at least you have to know the secret to access it, which will be enough to not make this essentially a public endpoint. It's going to work something like this.
And in here it noticed a typo. I said container, I should set contain. In here we have an invalid string that we have to replace webhook URL with the webhook URL from above when it actually embeds that in the copy button. So we can remove that part, correct. In here, as in the previous pull requests, we don't handle any errors here.
So yeah, we could wrap that instead of try catch and then catch errors. And in here it's telling us that node.tsx should have useClient. Since our previous ones didn't have it, I think we don't have to add it here, simply because its parent component is already a client component. And in here it's basically telling us the same thing that it did in the webhook part. Since this is the Google Apps script, later when we add some kind of validation to that webhook, we should also include authorization property here, so that it can access it.
All right, so great, great comments from CodeRabbit. Some serious security issues were caught here. Let's go ahead and merge that pull request, going instead of main and make sure to click on synchronize changes. This will synchronize your main branch with your newly merged one. Click on the graph to convince yourself that 22 was the latest merged one.
Amazing, amazing job. So very, well I wouldn't say challenging, but complex chapter with some new elements like Google Script and learning how all of that works. So we added Google Form Trigger node, dialog, executed a real-time channel and webhook. We created a Google form and we even discovered this Apps Script thing. We pushed to GitHub and reviewed the pull request.
Amazing amazing job and see you in the next chapter.