In this chapter, we're going to implement execution's history. So far, we've been able to run executions with either success or failure states, but the only way we've been able to look at the result of those executions is using the InGEST developer server. So what we're going to do now is implement a page where the user will be able to look at all of their current running or previously run executions and track whether they failed or whether they succeeded. Let's start by adding the schema for that. So let's go ahead and open schema, Prisma, and let's go ahead and go all the way down and let's create a model execution.
Let's give it an ID, default, cuid. Now let's go ahead and give it some timestamps. So each execution will have a started at date time and it will be automatically populated. Then we're going to have completed at which will be optional because it doesn't have to complete it can fail. We are then going to have ingest Event ID which will be unique and this is not executable so like that and we're going to have output which will be an optional JSON.
Now each workflow will have an execution So let's make sure that we go inside of model workflow here and add executions, execution, like this. And in order to fix the error, we need to create a proper relation. So let's go ahead and do workflow ID and let's make this a string and then let's do a workflow, workflow relation fields, workflow ID, references ID and on delete cascade. So if the workflow gets deleted, the execution history will get deleted as well. Of course, you can decide for yourself whether you want that or not.
That will be the behavior in my app. OnDelete has options like setDefault, setNull. So in this case, I'm using Cascade. Perfect. Now let's go ahead and create an enum so we can define exactly what kind of status execution can have.
So enum execution status can be running, success or failed. And now let's go ahead and very simply just add status execution status default running. So besides status timestamps in just event ID and output, Let's also make sure that we have an option to track error which can be an optional string and in order to increase the character length we can add db.text decorator and let's do the same for error stack because these two can be quite lengthy. So that's why we are adding this decorator right here. Great.
Now that we have this, let's go ahead and Let me see. So we have an index here automatically added because of the foreign key. And I think this should be enough. So now let's go ahead and migrate that. So npx prisma migrate dev.
Let's give it a name, something like executions schema. So after you've given it a name like this go ahead and press enter and that should synchronize your database. You can now close this and as always restart next.js and restart ingest. If you have it running, go ahead and refresh localhost. So now that we have this, we are ready to create this feature.
Let's go inside of the features folder and we can actually copy credentials. So copy credentials and paste it inside of features. Go ahead and rename it to executions. Oh, it looks like executions is already taken. So this should then be, let's see, well, we can actually keep it inside of executions.
That's right. No need to copy it. Let's actually use our executions folder. That makes perfect sense to do. So let's go ahead and start by creating server and then let's go ahead and copy routers.ds from credentials and paste it inside of executions server.
So make sure you open the routers.ds in your executions folder so close all other ones go ahead and name this to executions router and then let's go ahead inside of trpc routers underscore app and let's add executions executions router. You can import it from features executions server routers and save the file. Great. So this will be quite simpler now. Executions will not be able to be created via API, nor will be able to be removed.
They cannot be updated either, so none of this makes sense. These are like trace logs, right? You can only see them. You can't do anything with them. And you can also remove get by type.
So only two of them, get one and get many. Get one will be a protected procedure, which will very simply use Prisma.execution, find unique or throw. The only difference here will be that it will not use user ID like this. It will simply access its child workflow. Well, not its child, its relation to the workflow.
Like that. So yes, you can now do this with Prisma. Previously, you were not able to do this and it was a bit more complicated if you wanted to achieve this, but this is great. This is super simple and you can now very easily do permission check on a nested child like workflow which is really really cool. So now that we have that let's remove credential type, let's remove premium procedure, we don't need any of that, that's it for the get one.
Now let's go ahead and work on to get many so get many is quite similar except we don't need to search it so remove search from here and remove search from here You can remove the name property entirely here, and you can remove it in the count as well. Now let's go ahead and change this to be prisma.execution.findMany. So the where will have to be modified to look within the workflow for the user ID as well. The same thing we did previously and order by here let's use started at instead of created at and let's go ahead and add include workflow select ID true name true. So we have some more information to show on the user side.
And for this, Let's also change this to execution and very simply look within the workflow to make sure we are fetching only that user's executions. Great! Everything else can stay exactly the same. That was easy, wasn't it? So now let's go ahead and copy everything else that we have here in Credentials Server.
So that will include params loader and prefetch. And let's paste it here. So make sure you open prefetch and params loader from the new executions folder. And let's also go inside of credentials and let's copy params file and paste it inside of executions. So now let's go ahead and first start from the params file.
So just make sure you are inside of executions folder, params file. Let's go ahead and change this to be executions params and we can go ahead and remove search because search will no longer be available but let's go ahead and add workflow id actually we don't need that We can just use page and page size. Then let's go ahead inside of server in the executions folder and let's go ahead and do params loader here. So this will now be executions. Did I rename it executions params?
I did. So executions params and to fix this, let's just retype it and then this will be executions params loader using the executions params. There we go. Now let's go inside of prefetch.ts and let's fix this as well. So this will be trpc.executions.getMany, prefetch all executions, prefetch executions, trpc executions, drpcexecutions, single execution, prefetch, execution, drpcexecutions, get1.
There we go. So we have all of the server parts ready including the params. So now let's go ahead and do hooks. So I'm going to go ahead and copy useCredentials.params and useCredentials. I'm going to go instead of executions in the hooks here and I'm going to paste those two inside alongside use node status.
Let's start with use credentials params and let's go ahead and rename it to use executions params. If it asks to update imports, you can select yes. And looks like the only place it's going to update it is this one, use credentials from our executions folder. So this is the unsaved file, and this is where it updated it. So we will get to that for now you can save that file.
Let's focus on our just renamed use executions params and let's go ahead and just use executions params and this will be use executions params. There we go. Now let's go ahead and rename this to useExecutions.ts Let's go inside of useExecutions and let's change this to be useExecutionsParams Now this will also be a little bit simpler so we will have this hook to fetch all executions using suspense so use suspense executions use executions params drpc executions getMany. We will not have a hook to create new one, so we can get rid of that. Same is true for removing one, but we will have a hook to fetch a single execution using suspense.
So use suspense execution again, trpc executions get one. We will not have a hook to update any execution so we can remove that and we will also not have anything to fetch by type so we can remove that. So we only have two of these. Let's remove all the unused imports here. But I do think we might need, let's see, we have used suspense executions and use suspense execution.
I'm thinking whether we are going to need any non suspense hooks here but I think this should be fine for now. So now let's go ahead and create the page loader. So I'm going to go ahead and say source app dashboard rest credentials and I'm going to copy page.tsx. I'm going to go inside of executions here and well it might be easier to copy the content of page.tsx and then open executions page and paste it in here. There we go.
So let's go ahead and change things up from credentials to executions just make sure you are modifying executions page.tsx so require out stays the same but params should be loaded using executions params loader So make sure you change that import to use executions here and remove credentials params loader. And then for prefetching we're going to use prefetch executions with said params. So you can then remove prefetch credentials and make sure you have prefetch executions from features executions server prefetch. We can go ahead and replace the credentials container with just an empty fragment because we don't have an alternative for this just yet. And for the credentials error, we can just go ahead and do the same here.
We will add both of these later. And same is true for this to do list for executions so let's go ahead and remove all the unused things there we go now that we have that let's go ahead and check it out So I'm just going to make sure it's working. So when I click on executions right here it should load to do list for executions. Great. Now let's go ahead and work on the client side.
So I'm going to go ahead and close the app folder and go inside of features, executions and open the components folder. And then I'm going to go inside of credentials, components and I will copy credentials.dsx and I will paste it in here in the components. Let's go ahead and rename it to executions.tsx like that and then we're going to go ahead and slowly fix all of these errors and names. So double check you are inside of features, executions, components, executions.tsx. Let's start by changing this from hooks use credentials to hooks use executions and you can go ahead and remove use remove credential because we don't have it and instead you can use use suspense executions instead of this instead of you use credentials params executions params Instead of this, instead of useCredentialsParams, ExecutionsParams and import useExecutionsParams.
Let's go ahead and see what we need and what we don't need. For example, we don't need this search component at all. So we can just remove it. Instead of credentials list, it's going to be executions list. It's not going to be credentials, it's going to be executions, and it will be useSuspenseExecutions.
So to the entity list, let's make sure we are passing executions. Let's make sure we are using execution everywhere. And for now, let's just leave this as is. We're going to replace these two later. So when it comes to executions header, it's going to be quite simpler than this executions header.
It's not going to have any prop because it doesn't need the prop. And it's simply going to have a title executions like that. And a description, a view your workflow execution history and it will not have any of those two props. So looks like this is now throwing an error because it needs to have one of these. So yes it's expecting something here.
So can I add maybe? Okay, I don't know. I will look into it. But for now, yes, just ignore this error right here. So now let's go ahead and see what's up with pagination here.
So let's rename it to executions pagination. This will be used to suspense executions executions use executions params and then replace all of these three instances to use the executions constant. Perfect. Let's go ahead and rename this to executions container. Let's go ahead and use executions header.
Executions search which actually doesn't exist and we don't need it. And executions pagination here. There we go. Perfect. So now we have this.
Let's go ahead and change these three to be executions loading executions. So loading executions, error loading executions. In HandleEmpty, we don't need HandleCreate at all. You haven't created any executions. Get started by running your first workflow.
You haven't, I'm not sure if created is like the correct term to use. Not sure what else it should be. Yeah, for now let's use it like that. Instead of credential item, it will be execution item, data will be execution. And you should be able to import this from generated Prisma, so import type execution.
And instead of credential type, let's have execution status here because we are going to need that. You can remove entity search from the import, you can remove use router, you can remove use entity search, but keep execution status even though we don't use it yet. So let's go back to the execution item here. First of all, remove this, remove credential. We cannot remove anything from here.
And so what should be the icon here? Let's remove it for now. Let's just focus on rendering. So href should lead to executions, title should be data.status, and subtitle, let's go ahead and just make it an empty string for now. For the image here, let's go ahead and just do this, let's remove, on remove and is removing.
Okay. Now let's go ahead and create the subtitle and the image. So in order to create the subtitle, we need to add duration. So how long did it take to do this? So let's get the completed at and if it exists and if it does let's call math around new date data completed at dot get time minus new date data started at dot get time and then divide that by 1000.
Otherwise, just set it to now. And then const subtitle. Let's make it a fragment data workflow. Okay, so this will be execution and workflow with ID, which is a string and the name, which is a string. So why am I adding this?
I basically extended the type of execution to include two properties from its related workflow. How do I know I can do that? Well, if you go inside of routers in the execution server and look at get many, you will see that that's exactly what we do. We include workflow with ID and name. So that's what I'm doing here.
So that type safety knows that I can access data.workflow.name let's go ahead and add a bullet point here started let's add a space format distance do now which we have from date FNS. Data started at add suffix true. If duration is not null, In that case, let's go ahead and open a fragment here. Let's go ahead and render a bullet point again. Took duration seconds, like that.
So Now we have the subtitle that we can use, which will give the user some useful information about how long it took to complete this execution, if it completed. But for the image, we're going to need to create a little map here similar to this one. So instead of this, it's going to be a function. So constant get status icon. Status will be a type of not string, it should be a type of execution status.
Let's go ahead and switch based on the status. In case we get execution status.success, let's go ahead and return check circle to icon from Lucid React with size 5 text green 600. Like that. And then let's go ahead and just do the same for the other cases. So in case it fails, let's go ahead and use X circle icon from Lucid React.
And in case it's still running, let's go ahead and use loader2 icon. Just make sure besides the color and the size you also give it animate spin. And in case we cannot find the status for whatever reason, oops, let's go ahead and give it a default of clock icon from Lucid React. So just make sure you have added all of these icons. You can now remove image from next image here.
So that's it for the execution. Oh yeah, we actually have to render that here. So let's do get status icon data status. There we go. And now that will render one of these.
Now we can scroll all the way up here. Instead of executions list, change this to be execution item, executions empty. There we go. Great. So the only thing left to fix is the entity header because right now it's expecting either on new or new button href but not the option to not have any of them and I think that fix is actually quite easy so instead of entity header you just have to add a question mark here.
And save it. And that's it. Now it works as expected. That was a bug, actually. Great.
So we now have all of those components and we can now go back inside of the app folder dashboard rest executions page dot TSX and we can now add all of them. So let's add executions container here to encapsulate the whole thing. So from that features executions components executions. Then let's go ahead and add whoops Executions error. Then let's go ahead and add executions loading.
And finally, let's add executions list. And once you do that, you should see no items obviously because even though we have run some executions, we never kept track of them. So in order to see this happen, we have to go ahead and revisit our functions.ts. So I'm going to go instead of source, ingest folder, functions.ts. So how do we make sure that every time this execute workflow fails, we create a new execution?
Well, we do it by first making sure that every single time this is a run, we start by creating an execution. So because of that we are also going to need to always have an ingest event id event.id but there is one problem with this so event.id I don't like that it can be undefined. So I discussed this with ingest theme and they did tell me that this will always be created for every workflow. But if you want to, here is what I did, which kind of gave me a peace of mind. So what I did is I went instead of ingest utils.ts.
And since I'm using send workflow execution everywhere where I need to execute this, I can very easily make sure that every single one of my InGIS jobs has an ID by simply using cuid2. I'm just not sure if we've used this before. I think we did. CreateId from parallel drive cuid2. And then very simply, you can see it accepts id property.
We can just do this. And now for 100% we can say that every single one of our ingest executions will have the ingest event ID using event.id. So let's go ahead and also check if there is no ingest event ID. Let's go ahead and say event ID or workflow ID is missing. If you want to be more specific, obviously you could separate those two errors.
And then before we sort our nodes, let's just do a super simple create execution step. It's an asynchronous function and just return prisma execution create data with workflow id and ingest id. That's it. We don't have to pass the status because we have a default status of running. So again, this is my kind of architectural choice.
I'm not sure if you like this. If you want to, you can make it optional and not have a default. But since I am always going to create the execution when it's literally starting to run, I'm always going to pass running as the default. So in my case, it makes sense. In yours, maybe it won't.
So yeah, just kind of think about this and how you would like this to behave. I like it this way. So now every single one of our execution will have this happen. So what do we do if it's successful? Well, that's simple.
We just have to go ahead all the way after this for loop and await step dot run update execution async return prisma execution update where ingest event id is matching data status execution status which you have to import from generated prisma dot success complete that new date output will be the context basically everything that was created after we ran all the nodes, after all the variables have been added to the context, we're going to store that here. And I think we can also make it a bit more specific by also adding workflow ID. We don't have to, I think, but we can do it. So this is for the success case, but what if it fails? How do we handle that?
Well, you can choose how granular you want to be with this. You could go into every single individual executor here and then create failures from there. But there is a way that you can catch general failure of this function right here and you do it here so on failure like this and it's an asynchronous function and you have access to event and step. Let's go ahead and do return prisma execution update where ingest event id is event.data and yes, now you have to be a little bit specific you have to access event again and then id and in here let's go ahead and let's add the status and make it failed and we have to add some useful error information so let's go ahead and populate the error using event data error message and error stack using event data error dot stack. There we go.
So we now have that ready. Let's go ahead and close all of these. Let's go inside of workflows here and let's try running some workflows. So they can either fail or they can be successful, whatever you want. So I'm going to execute this one and I'm gonna go inside of my executions right here and I will refresh and there we go.
I have one which is running and then it is a success. Amazing. So let's go ahead and just fix this. I'm going to go inside of my executions.tsx And I'm going to go inside of my execution item. So what I don't like is that the title is just a large yelling status.
So there is a way to format that very easily. We can create a function. Let's do it here. Const format status. Status is a type of execution status.
And let's return status character at first index plus status dot slice everything after the first index to lowercase. So basically we're just going to capitalize it. Maybe you can do that with CSS. I'm not sure. I'm just used to doing this.
So let's just wrap this and this should give us... There we go. That looks better. That gives us like a readable thing. So now let me go ahead and fail this on purpose.
So I'm going to open this and I will make sure I have a completely invalid URL here. Save. I'm going to save up there. And then I'm going to execute that. So now I should have one execution which is a success and then I'm going to have one execution which is a failure.
There we go. Both of these are now working And I'm fairly certain that both of this should now also show that yes, you can now see a new step, create execution. And let's see, finally, is it okay, it looks like you can't see those steps where it created the word updated the execution to the failed status. That's what I was trying to say. But you can see this step, which is update execution when it succeeds.
So you should have that extra step now. Great. So what's not working yet is the ability to look at the deeper view of the execution. So let's go ahead and do that and finish our executions. So let's create execution.tsx inside of features, executions, components right here.
So we actually don't have to copy it from credentials. Let's just create a plain new one execution.tsx, so a single one. Then let's go inside of executions right here and let's just copy format status and get status icon. In order to do that we have to add all of these icons from Lucid React, we have to add the status type and I think that's it. So now what else are we going to need here?
So let's finish the imports while we are here. We're going to need date FNS format distance to now. We're going to need links to redirect to the actual workflow. We're going to to need useParams. We're going to need useState from React.
We are going to need button from components UI button. We're going to need the entire card, so card content, description, header, and title. Then We're going to need the entire card, so card content, description, header, and title. Then we're going to need the entire collapsible, which I believe is the first time we're using this component. So it's from Chassis and UI.
And we are going to need use suspense execution from features executions hook use executions. So just alongside use suspense executions we are using this one to fetch a single execution. Great. So now that we have all the imports we need, we are ready to export const execution view. And let me just check in credentials.
Did I maybe have a better name for this component? I didn't. It's just credential. Okay. Then this will be just execution.
Let's go ahead and give this a param hook. Let's go ahead and extract execution ID here as string. And in fact, I remember this is the same mistake from before. We can just do execution ID here. That's simpler if you ask me.
And then you don't need params nor execution ID. We're just going to pass this from the server component as a prop. So you can go ahead and remove use params. Perfect. Then let's go ahead and fetch this.
So use suspense execution using the execution ID and alias data to execution. Then let's go ahead and create a simple state total, show stack trace and set show stack trace. Now let's go ahead and go inside of executions.ts and just copy the duration script. So in order to generate the duration instead of data just alias it to execution. So execution dot completed at and then do math round with execution completed at and start at or fall back to null.
Now let's go ahead and let's compose this. So we're using card. Let's give it class name shadow none. Then let's go ahead and add card header. Let's add a div here with a class name flex items center gap 3.
Inside let's render get status icon execution dot status. Then let's go ahead inside of this and create a new div and this div will have a card title which will format status execution.status and a card description execution for execution.workflow.name But now we have a type problem here. So let's go ahead and see how can we fix this. Well, this actually isn't just a type problem. This is an actual problem.
Let's go inside of useSuspenseExecution and let's go inside of trpcexecutions.get1. And besides this where, let's go ahead and also add include here. So I'm going to add include workflow ID true not include my apologies select name let me just check what I am doing just check what I'm doing wrong here. Okay. I see it needs to be inside of include.
And then this is inside. There we go. So include workflow but only select ID and name. So the exact same thing I could have just looked down here. The exact same thing we're doing in get many I forgot to do in get one.
So that should automatically fix any type errors here. We can now access this in the card description. Perfect. So that's it for card description. Now let's go ahead outside of the card header.
Let's open card content. Card content will have a class name of space y4 and in here let's go ahead and let's create a div with a class name grid grid columns 2 gap 4. Let's open a div a paragraph workflow. Let's go ahead and write a class name, text small, font medium, text muted foreground. Then let's add a link.
Inside of the link we're going to refer to execution.workflow.name and href will go to workflows execution.workflow id. There we go. Make sure you're using backticks and don't misspell workflows. Let's go ahead and add a few more attributes to the link property. So it will be prefetched so it's faster and class name text small hover underline and text primary.
And when I say let's use prefetch so it will be faster, what I mean is that it's going to prefetch it automatically. So even when users, if users don't click on it, so it is a compromise. It's not just a magical speed up thing. That's why it's optional to add. So for this exact scenario, it's okay because it's just this single link.
But you should be careful when adding prefetch to like a generated list of a billion results because that will definitely populate your network request tab. So be careful about using prefetch. All right, so now we have a link and I think at this point it might be easier if we render this so we can actually see what we are doing. So I'm going to go ahead inside of my app folder dashboard rest executions execution ID page.tsx And let's go ahead and do the following. In here, I'm going to do div class name padding for on medium devices px10 py6 height full.
And let's actually just copy what we have inside of credentials for credential ID here, since it is identical. So just copy it here. Paste it. There we go and now in here let's add hydrate client from the RPC server let's go ahead and add error boundary from react error boundary suspense from react like this and let's go ahead and add execution view and execution ID will be params dot execution. Oh we have to await params.
Oh we have it right here. Whoops. So execution ID. Great. Let's set fallback here.
I think we can just reuse executions error and fall back here executions loading. So I'm borrowing these created inside of executions for the list, right? Because they're the same. I'm not creative enough to create different loading and error states for single execution view. So yes, execution view is imported from execution file and loading and error from executions file.
Great. So some error is happening here because instead of execution view, we forgot to map this as use client. So yes, your execution.tsx should have use client at the top. And there we go. You can now see how this looks.
But we are not pre fetching this. So let's just make sure we are doing that. So instead of page execution ID here, we can just pre fetch. Yes. Pre fetch execution, execution ID.
So make sure you import a prefetch execution from features execution server prefetch. There we go. So now it should leverage the server component and the client component at the same time. Now we can focus exclusively on execution view and actually see what we are developing. So in here we have the status icon, the status, the name of the workflow, the link to go to that workflow if you click on it, it should redirect you to that workflow.
And now we're going to add stack trace down here right after we add the duration or when it started. So let's go ahead still instead of card content here after we end the link and After we end this div, open a new div, add a paragraph here, status, and another paragraph, format status, execution.status. There we go. Let's go ahead and give this paragraph a class name, text small, font medium, text muted foreground. And let's give this one a class name of text small.
Now let's go ahead and duplicate this. This one will be started and this will be format distance to now and use execution dot started at and add suffix true. So if you take a look, you will see the status here when it started the workflow, right? Just a grid of information for this execution. Let's duplicate this again but this one will be conditional so if execution dot completed at exists Only then go ahead and render this.
Otherwise, render null. So this will be completed. And this will be execution.completedAt. For example, this one will not have that visible. But if I go inside of my successful execution, it should have it visible right here, completed 17 minutes ago.
And you can also see how it says that it took six seconds to complete. So that's a cool thing to look at, in my opinion. Now let's go ahead and copy this again. So this one will also be conditional. And let's just check if duration is not equal to null then let's go ahead and let's just render the duration and add s as in seconds and change this to duration So this will still be visible simply because you can see simply because we selected the success one right so it lasted for six seconds but I think in the failed one you are not able to see that information.
And now let's go ahead and copy this. And let's go ahead and do if execution dot ingest event ID event ID like this and simply render execution dot ingest event ID. So this is for some debugging information if you need it. Here it is event ID. But I think event ID will always exist.
So we can just safely render it, yeah. Event ID is not optional. And now let's check if we have execution.error. In that case let's open up a div here with a class name margin-top of 6 padding of 4, background-red 50 dark, actually no need for this, let's just do rounded-medium-space-wide 3 then open a new div inside then open a paragraph which will render the text error let's give this paragraph a class name of text small font medium text red 900 margin bottom of 2. Below that another paragraph with execution dot error rendered inside.
And then a class name text small text red 800 and font mono like that great but now we're gonna go ahead and make it a little bit more fun let's also give this okay it already has a rounded MD great so outside of this div but still inside of the whole error container I'm going to check if we have execution dot error stack if I do I'm going to render a collapsible and I'm gonna use open show stack trace On Open Change Set Show Stack Trace. I'm going to add collapsible trigger here and I'm going to render a button inside and if show stack trace is active, I'm going to render hide stack trace otherwise show stack trace. I'm going to give this button a variant of ghost, size of small, class name text red 900 hover bg red 100 great let's give this collapsible trigger as child property outside of the collapsible trigger, add a collapsible content. And inside, let's add a pre-tag. And let's render execution.error stack.
Let's go ahead and give it a class name, text extra small, font mono, text red 800, overflow auto, margin top of 2, padding of 2, BG red 100 and rounded. There we go. So let's go ahead and check this out. If I click this it will show me the error. One thing I don't like is that this is not taking enough space in my opinion.
So let me see. Maybe I put it in an invalid container. So just a second here. Card, we're doing status. So this is the grid thing.
Perhaps this should be outside of this. Let me just check if I'm correct. If I maybe end this div here and then go to the end and remove one div. Yes, I think that's what I wanted to do. Basically, let me revert this.
Go ahead and find this div which starts the grid. Right now, this div ends all the way here right before the card content so remove that div and instead close it just before you start doing conditional execution dot error there we go And this is just TypeScript server error. There we go. This looks better now and you can see more details inside. But now let's go ahead and just do the same thing for output.
So if we have execution.output Let's add a div with a class name, margin top of 6, padding 4, background muted, rounded medium, paragraph with the text output. With the text output. Let's go ahead and give this a class name. Text small font medium margin bottom of two a pre tag JSON stringify execution dot output and then null and two which are properties to make this JSON more readable. Text extra small, font mono, overflow auto.
There we go. So we can only test this in a successful node. So I'm going to go back here. Success. And there we go.
Output my slack message content. Hello world slack. Amazing. You can now see the history of your executions. I believe that is it, that's all we have to do here.
One thing that I like to check, but this time I'm not sure I will be able to check simply because there are so many things inside of this executions folder. I think I made a mistake with adding these nodes in here. I think I should have a separate feature called nodes and then just have all of them inside and also keep their channels with them because this is kind of neither here or there. And what I'm referring to as executions is very inconsistent right Because I also have triggers for some reason, but they are technically just nodes, right? So it might be like give yourself a challenge and I would improve this structure now at the end of this whole project by creating a new feature called nodes.
And I would keep both triggers and what I call executions in that place and then I would no longer refer to them as executions. Executions would just be what we just defined in the schema, right? The result, success or failure, right? And everything else would be nodes. And then some nodes will be used as triggers, yes.
And other nodes will be used as executors. So yes, the words are kind of confusing. The terminology is not that simple. But yeah, I think most of you feel like something is off here by having these nodes in here. And also, inside of the executions folder, I have this lib where there's the executor registry.
Yeah, there could definitely be a better place for this. It's not you, it's me. I made an invalid architectural decision here. You can give yourself a task to refactor that I would highly suggest it to get even more familiar with the code, but do it at the end of the tutorial so you don't run into any bugs. Excellent.
So let's go ahead and check if we maybe forgot something from our task here. We added the schema, the router, the hooks, page loaders, client, entity components, pagination, loading, error, empty and we added execution records in Ingest if they fail, if they succeed and when they start. So let's push this to GitHub, 27 executions history and then we're going to see what CodeRabbit has to say. So new branch 27 executions history. I'm going to go ahead and go inside of my source control.
I'm going to stage all of my changes. 27 execution history commit. Let's go ahead and let's publish branch. Once the branch has been published, we can go ahead, create a new pull request here and review it using CodeRabbit. And here we have the summary by CodeRabbit.
Release notes. New execution dashboard with workflow around history with pagination. Track execution status with visual indicators. Running successful or failed. View detailed execution information including timing, duration and output, access error messages and stack traces for debugging failed executions.
And let's go ahead and take a look at the diagram. So what is up here is essentially just the prefetching and how it works. We've already seen that a couple of times. So this is the interesting one. When we trigger a workflow using the send workflow execution util, we go ahead and immediately create an execution with a default status of running.
After we process all the workflow nodes successfully, we update the execution with a status of success, completed at, and we pass along the output which was transformed through all the nodes. But in case the workflow fails, we update execution to failed with error and error stack. Let's take a look at the comments here. Saying here in schema Prisma, it is recommending adding a composite index of workflow ID and started at with descending sort. That's a good idea to add actually simply because we are using started at order by instead of get many query.
So yes it could definitely improve performance if this database record grows large. In here it is actually not correct. We do not need to await prefetch executions. There is nothing that this will return. This is a void.
So there is no need to await this. Prefetching is a relatively new concept to LLMs, so a lot of them get this wrong. But no, you do not need to await prefetching. In here, I think we already had this comment once, the first time we implemented it, basically an improvement of our Zod rules for page size and page. Other than that we are golden.
Let's go ahead and merge this pull request. Very good suggestion to add the index to speed up those queries later on. We can now go back to our main branch, go ahead and click synchronize changes and once that is complete as always I like to double check by clicking on the graph. There we go, 27. Amazing.
Let's go ahead and mark this as completed. Amazing, amazing job and see you in the next chapter.