 Okay let's go. Once again, welcome everyone. My name is Sakmabicki. I'm a senior software engineer in Apsilon, a company focused on providing solutions for various other companies based mostly on Shiny. But also we do some other software engineering related to R, but our main topic is Shiny. And today I want to share with you a glimpse of a tool that we developed during the years, maybe not during the years, but the idea of having such a tool started a few years ago. And the thing that I will show you in a minute is the outcome of those years. So basically what is Rhino? Rhino is a framework for Shiny applications that keeps in mind that Shiny is and are in general, it's just a programming language. So if it's a programming language, then let's keep all those best practices developed through the years of software engineering and put it here. Let's use it to produce something that is reliable, that is maintainable, and let's treat Shiny and R as it should be treated as a regular tool that can be used in enterprise. So I bet all of you already heard about it, because we released it a few years ago. And the general idea I want to show with you is the Rhino is a framework that's kind of a toolbox. It is based on various different tools that are known. And it combines them and shows how you can use them to test things, to write things in a better way to keep your code the best as it can be. Of course, those are just our ideas how it can be done. There are other tools that do similar things. But this is the way that we came and this is something we want now to share with you. A few things about this workshop. We have only one half hour, so this will be a pretty fast one. And to be honest, I have a lot of things already prepared. So probably you will not be able to keep along with what I'm showing on the screen. Don't be afraid. Here is a link to the repository. Now it's empty, but I will post everything there. A few things about the requirements. I assume that all of you have already installed Rhino. I also asked for Node.js and Yarn. Those two are optional, although without it you will not be able to follow everything. Most of Rhino will still work, but without some cool features that I will show you. For now, we are all muted. If you have any questions, please ask them using the chat. I'll try to monitor that and answer them on the fly. If you have some bigger ones, please keep them till the end. I hope we will have some time to answer them or at least a few of them. Do you have any questions right now? If yes, just write them on the chat. I can't see anything, so let me go to the actual coding. This will be more or less live coding experience. So let's start. First thing, that is kind of obvious. Before that, we will have the recording on our constant YouTube. Node doesn't really matter. Both versions 16 and 18 are okay. I know that Rhino also worked with version 14, so this should be fine. Any new one should be okay. First thing, we need to start Rhino application to create an initial Rhino application. You can do it by running Rhino init function, or if you use RStudio, you can simply create a new project. If you have Rhino already installed in your system, you will see something like that. For this workshop, I will use Visual Studio Code, but let me first create an application. As the input for Rhino init, you just need to give your application name or your product name. This is actually simple, so let's call it our medicine. And something's happened. We will have our profile. We will have some packages here. As you can see, Rhino forces you to use our end. We will have some structure created. So let's quit. Let's go here and let's start coding. I trust myself. Okay, so first of all, is this size of the screen okay, or do you want me to enlarge it? Maybe I'll do it anyway. Okay, what do we have here? We have App.R, which is an entry point to our application. And this is something that will run your application. If you are in our studio, you will see a button to run a Shiny application. RStudio perfectly recognizes that as a Shiny app. If we want to do this, okay, I need to make it smaller. Sorry about that. It does not work. Sorry. Okay, we can go with a different mode. If you want to run your application, you can just run Shiny. And what you see is an initial Shiny application. So what happens here? Rhino starts the application based on the main file. This is the initial module that you get when you start a new Rhino project. And this is something you should be familiar with, the server part and the UI part. What is different comparing to the regular Shiny? You already have it wrapped in modules. Second thing different is this part. In Rhino, instead of using library, we use box. What's the difference? This thing will load Shiny, but will load only those functions from Shiny. So it's our page, module server, and this is local. So those functions will be available only in this script. If you know Python, this should be kind of familiar to you, because box is based on the import idea that is known in Python. We'll get back to it in a second. What else you can see here? There is our end of this here. So you already can see the full library of your project. And you have our end of the log. So you know everything about all the packages that are here. You get some configuration files. You get some initial structure of your application. A few directories. We'll get to it in a second. Some initial JS file. Some initial CSS file. You get a configuration for your CI. This will come in a minute. Okay. So the initial application is pretty simple. Just hello. So let's start with having something. Let's add a table. And to do that, we need a table. We need a content of the table. And we will need to put it inside the application. So first, let's create a function that will produce a table. I will choose the logic directory because it's a place where you should put your application logic, which is not reactive. Let me call it just table. By the way, this expression between logic and view, which I will use in a second, is just a suggestion. So if you want to use some different names or a different setup, feel free to do that. All right. So now I need to write kind of a function that will produce a table. So I will call it again table. It should take something and do something with this dataset. So now we need a library that will produce a table. There are multiple solutions here in Shiny, DT, our handsome table, so on and so on. I will choose react table first. I need to install it. And this is the first place where Rhino uses a well-known tune, which is aren't, but adds a small twist to that. So first, let me install react table. It should be pretty fast because it's cached. And now what I should do is probably do the snapshot. So to record the react table in my dependencies file, in my aren't block file. But the general way aren't work is that it scans your whole project setup, project directory for all library calls, double columns, things like that. Here in Rhino, we slightly change this solution. There is a file called dependencies, which is the only file that will be checked for library calls. So if I, for example, I'm testing various libraries for table, like DT, handsome table and so on. And finally, I came up with the idea that, okay, the react table is the best one and I will choose that. I will not end up with a mess in my aren't block in my environment because only things that are here will be stored in aren't block. Now the repository is still empty. It will, I will push things as I'm showing you the application. So for now, it is empty. It will be populated in a minute. So, okay, you see it found that react table is used and it stored it in my aren't block. Okay, we've got this. So now we need to let this script know that we are using, we are going to use react table. So as I already told you, you're not going to use library, we are going to use box. And this construction will tell our that, okay, we are using this package and we are importing this particular function into this here. Okay, there is one thing missing, which is export. You probably know this keyword from package, from developing packages. This is exactly the same. And now the, if I will try to use that file, I will know that, okay, table is a function that should be available. So in fact, you can think about this very, very small script as a very, very small package. I can have additional functions here. And this will be hidden. Only table will be available if you will try to use that. Later, if I, if the product is getting bigger, maybe this will be part of a package. And okay, I can see a question about the description file. Yeah, the problem, we try to use the description file. This is still in progress. The problem is that aren't, if it, if it finds the description file, it will store the libraries, not here, but in a general location. So this can cause some problems later with the, because the library is not fully stick to the project. Okay, the triangle, this is just the, the native pipe from, from the new R. VS code is just, because I am using, if you record fonts, so this is automatic ligature that will be shown here. And if you, for example, use things like that to also be, be modified. Okay, benefits to use VS over RStudio does the matter of what you like. I like VS code more. It's better to, to use it if you work with different languages. And extensions are super cool. Okay, let's, let's go back to the, to creating a table. We have a table. We need something to put inside. So let me create another file. And I'll just use a data set that is available in Rhino, which is called Rhino's and will, inside there will be, there's, there is a bunch of facts about Rhino's service. So here is an exception regarding the box. Normally I should use here dollar sign. This is a, an alternative to this structure. Instead of having something like that, I can have something like that. Now I'm importing whole package. It's not available. Or the functions are not available directly, but they are accessible by using something like that, the dollar sign. If I want to have everything in, in this script, I can do something like that. I, this is not a good practice. You lose all those, all the benefits, all those benefits that, that, that are given you by using box. But yeah, you can do that. Okay. So here, this is an exception because for data sets, the dollar sign doesn't work. And the last part, I need to use all of this in, in the, in my application. So let me quickly show you how this can be, how to approach that because now we have two scripts, table and data. And we want to create a new view with that table. So Rhino takes everything, including the main part, and wraps it into shiny modules. So this is one leg of the, of the encapsulation. The other leg is box. I will help you a little bit more in a second. First, let's, let's make this work. So, yeah, we need to simply render those, those things. And we need to be able to use those two scripts that we just made. Okay. Box can not only use, be used to call libraries. It is also going to be, it's also used to load your scripts. Instead of doing source something, you can do something like that. This points box to upload the table, which is the file we just created. And you, and it understands now that the table will be a function from that script. So why you, you should do this instead of having a global source. As you can see, I hear you're stable, table, table, table. This is an over-engineered example, because this is just a very, very quick workshop. So this is, this may not make too much sense here in a such small application. But if you have a very, very large application with 20, 30 files, you can imagine that there can be a mess. You go to a file and you will have, you will get some things like, like that. And you will start thinking, okay, so I have a function fetch date. So where does it come from? And here's the answer. It's pretty clear immediately after you enter such a file that, okay, I'm using here those two scripts, date and table from logic, those two functions. And I need shiny and direct table libraries. So later, if you get to this application in six months or any new developer gets here, they will immediately know what's going on. And you avoid various conflicts regarding the naming. Okay, so this is a more or less full module of, that can be used in our application. Finally, we need to add it somewhere, add it to our main part. So first, of course, we need to load it again. This is the table. But because this is the path, you already know that this is a different table that we have used in a second ago. Okay, we don't need this initial part. What we need is, we need to call the module that we just created. That's it. We don't need those things. Let's check if, of course not. Okay, so we missed something. We missed reactive. It happens here. So here, the data is fetched inside reactive. This is something that will be useful in a second. Okay, I see a few questions here. How to create a view table are just create a file and this, I will post this in a second. This will be just a regular Rhino module if you have, if you are using RStudio, there is also an add-in for that. So best thing would be to wait a second and let me give me a second. I will just quickly push everything to the repo. Okay, so, sorry, I accidentally sent this to just one person. Okay, once again, this is a link to the repo. You should be able to see the code there. Okay, let's get back here. The application doesn't look very well. The table is kind of, makes not a lot of sense. So let's upgrade it. Instead of having just a plain rack table, let's add some data manipulation. I will simply use the player and tider to do that. And what I'll do is I'll make columns for each Rhino species if you do notice. This is a table with a population of various species of rhinos by years. So what I'm going to do is I'm going to make a column for each species and around it by year. What I miss is of course, installation of packages. I need to save them in my RnVlog file. So I need to add them to the dependencies, the snapshots. So it's all stored. Okay, it looks a little bit better. So we have column for years and for each species of rhino, we have population. Of course, this is, we have some blank spaces, but that's okay. Okay, we have a table. Maybe let's add a chart. So again, same as previously, let's create a, for that, for the logic regarding the chart. Again, this is an overhead. So normally I wouldn't create a logic for a chart that can be created in a single file. This is just for the sake of the workshop. So again, I will use each R for R. I will need, I will quickly copy a prepared function. So what we want to do, we want to have data, probably the same dataset as we used in the second ago. We're going to group it by species. We want to have a chart by year. We want to have a chart where the line will be marking the population of species. And x-axis should be here. And we can add tooltip just to make it better. Okay, so we already have a logic for the chart. Not much logic, but a logic. We need a view for that. Again, this is much overhead, but this is just for the sake of the workshop. And we will need some functions from each R's from R. And of course, we'll need to import the logic that we just created. So okay, let's use that. Let's create a module with the DIY part, which will have these output. And of course, renderer for the chart. So okay, we have data. Data will be the same as in the table. So if you go here, this is a pretty simple function that just loads dataset from a package. But we can easily image that, have an image that this can be a call to the database. This can be very expensive. This can take time. So there is no point in there is no point in doing this twice. Let's simply pass the object here. And if we want to do that here, we probably also want to do the same. And this, this will be just a part of our main function that will, our main module that will fetch the data and pass it to the table. We also need to remember that reactive is now here in this file, not imported. And what we need to do is we need to import here the chart. One thing that is cool in box is that you can have a trailing comma. So later, if you push this to the git repository and someone will add something here, only this line will be modified. So this makes things much more clear. Okay, let's call the UI. Let's do the same with this part. Sorry. And what's missing is the package. One more thing is the dependencies and the snapshots. And of course, there is a problem with the main file. And there's still a problem because I did a typo. What happened now? First, we, I missed the export here, but it was working because if there are no exports, box assumes that everything is exported, but the missing part is here. We, because we started fetching data here, we need to do a logic. Sorry. We need probably something like that. Okay, we've got everything. We've got a chart. We've got a table. Okay, so this works. We have two components. We have an over-engineered structure for such small applications, but it's in a larger scale. It will give you some, give you a lot of benefits. So again, for a small application, this may be an overkill to use the things. For a larger, it immediately starts to make sense to keep it like that. Okay, so yeah, let me quickly push everything to the repository. You can have access to that. Okay, so what can we do next? Okay, let's see here. There is a comma here. This should be a year. So this comma makes no sense. And this is because this is an integer. So it starts automatically adds something like that. Let's get rid of it. And I will show you how Rhino can interact with JavaScript code. So if you put the chart logic here, you can add something called formatter. And this will take all values and somehow format it. And this needs to be a JavaScript code. So I will simply use HTML Widgets.js to convert a string to a JS code. And normally, I would just write something here. Very, very short snippet of JavaScript code to remove this comma from the chart. But it's a great opportunity to show you how JS works in Rhino. So here, there is an initial file that should contain your JavaScript code. There is a whole directory for that. Let's use it. And let's make it cool. So let's use some features from newer JavaScript. Sorry. Okay. So we need to take value. And we need to make it a string. If it's a string, then it just for our shouldn't convert, shouldn't add this comma there. So there are multiple ways to do that. Let's use string interpolation. Again, overkill here, but why not? Okay. So we have this function. What's next? Normally, in Shiny, you would add a tag script to the head of your application to load the script. And this would be accessible. Rhino kind of does it for you. But again, with a twist. Because the things that are here are not going to be directly put in the application. The location of all files that will be accessible from the browser in your application will be here in static. For now, there's only the icon. But there is a very, very easy thing that you can do. And this is the first place where you need node. So what I have to do is I need to build a bundled JS file from what I just wrote. To do that, I need one more thing. I need to export that function. So similar to what is done now in our files, in JS code, in Rhino, you need to export things that you want to access from outside of the script. So without this export, I wouldn't be able to access this format here function. Now what I'm going to do is I'm going to build JS. Rhino will install node. So this is a plate, node module. So this is a moment where you need that. If you don't have a node in your machine, then you need to do this the old way just by adding scripts to your head. What is this? Simple. This is the arrow, equal mark. And again, this is just the way that JS code formats it. And what just happened, Rhino created a new file in static.js with a JS code that is minified in a single line that is simplified that again, this is an overhead because this is just a very, very small function. But in a large code base, this is a common practice. And this is something natural for people in JS world, not so natural in R, but Rhino tries to bring all those best practices here. So this will be faster. This will allow all the browsers to use this JS code, things like that. Under the hood, there is a webpack, there is Babel. You can learn more about it in line documentation and there are links to those tools. But let's check if this actually works. Of course not. And it does not work because I created this function, but I didn't use it actually in the chat. Sorry about that. So here, I need to use this format here to give the information that I use this function to format those files. But this is another difference between regular sharing and Rhino. Now everything that is exported can be found under app. So if I do something like that, I hope that this will work. Okay, finally. As you, I hope, can see that there is no comma here. Okay. One more thing regarding JS code. This is not very cool because we have these are three lines. I used arrow function, which should be probably written slightly different. And regarding the style guidelines for JS. So what Rhino can do for you? Rhino can check if your code looks okay and does not. We have missing semicolon. We have too much things inside the row function. Probably it works as you saw, but let's make it better. And I don't need to do it backhand. Link.js has a nice argument fix. And you can see it looks much better. It's just one liner. Looks nice. Okay. So that is regarding JavaScript. If you want, again, there is documentation for that in Rhino. We are working on that constantly. So hopefully there will be more in the future. There are also some discussions on the GitHub page of Rhino. So please check that. And yeah. Let me quickly add this to the repo so you can access it. It's there. Okay. What's next? The application still is not looking the best. So we should probably add some styling here. And to do that, I will quickly add a few things in the main. So what I want to do is I want to have somehow this chart and the end table inline and maybe add some border or something like that. First, let's add some helper functions. As you can see, you can add them in the main. Normally for such components, I would probably create another directory called components or UI components. But for the simplicity, let's add it here. I have added div. So it will ask for that later. So let's give a box the div that it wants. And let's wrap those things in those functions. Okay. So this is now in the grid. Let's add it to cart. And as you can see, the only thing that will now happen is we'll get those classes in those divs. So we can style them. I can do one more thing. I can add title to the page. Okay. Let's go. Okay. We've got title. If you check those elements somewhere, okay. Here we have class cart and here we have class grid. So we can do styling on those elements. And obviously I have some CSS prepared. And for the CSS code or the styles, we have another directory called styles. And this is another tool that is used by Rhino and is incorporated into the code. It's SAS. It's a modern language that uses a little bit enhanced syntax to produce a regular CSS. So here you can see one of things that are available, which is mixing. So I can create something like that. This will give me a shadow. And later I can include this code in the specification for the cart class. Later I could have another class like box and I will also be able to include the same thing here. So there's no need to write it. Why is the same? There are multiple various things you can use. There is nesting. There are functions. So this is really cool. If you want, please check the documentation of SAS. And this is something. Maybe I will remove this thing. This is something that is not a valid CSS right now because the regular CSS does not know what is mixed in, what is included. And again, same as this, this is just a file that is somewhere here and application is not going to see that. And similar to what happened a few minutes ago, we need to build proper CSS from this. As it was done with JavaScript, this can be done also with the SAS code. And here is a place where you can either use Node or R package. I encourage you to use Node if you were able to do things with JavaScript, you should be able to do things with SAS. So if you don't have Node on your machine, you can go to Rhino-Yaml and change this SAS configuration to R. And this will allow you to use the R package. But the problem with R package is that it depends on library that is deprecated. So it will not give you the newest features that SAS is bringing to you like you will probably need to still use import to import other files while now it is encouraged to use a keyword. There are some things that are changing and you will be stuck with the old version. Okay, a question about this. Okay, this is just extension to Visual Studio Code. This is one of the reasons I like this code. I can manually pick up a correct color. And how I created the CSS file so quickly. So the CSS file was here. It was empty. And I had these styles prepared. So to quickly guide you what's going to happen. There is no margin around here. Looks pretty odd. So let's add a margin to the whole application. Let's use a grid display to inline those two parts. And let's add a shadow and some padding to each part so it will look better. Okay, this is regarding the shadow. This is just a snippet to do that. So, yeah, to be honest, I copy pasted it. So this is not something that I know by heart. It's just a matter of how white should be the box around the object. Okay, question about this one. No, this can have only single value. So if you can use Node, please leave it as it is. If you have to use our package, you can change it to R. But there are no other options and you can have only one option. And this controls what's going to happen here. And this is the similar function to what happened with JS. So what is new right now is this file. So you can recognize all those things that were there. And if you check the definition of card, it now has this shadow. And again, same as with JS, this file is already imported by Rhino. So there is no need to create a link to the style sheet in the head of application. This is already done by Rhino. And, okay, it simply works. So now we have a nice box and inline things. Okay, so this is it. And same as it was with the, with JS, you can also have a linter or SAS. And this is something that requires Node. So if you are using our package for SAS, sorry, this is not going to going to work. Now it works fine. It looks okay. If I miss semicolon, it should be able to spot it. Okay. And same as with JS, it will be able to automatically fix it. Okay. Let me add it to the repository. Okay. I have a few more tools to show in Rhino. So, but this will require a little bit more interactivity inside the application. So I want to create another module that will be responsible for filtering species. So now we have everything here. I want to add another card here that will allow you to select which species you want to have. So again, same as before. Let's add some logic to that. This is purely related to the data. So I will create a function to filter. It needs to be exported. And let's just use the plier. And as previously, we of course need to have the plier imported here. Okay. So now this will work. This should simply filter our data set with what will be given in some kind of input. And let's create another tab for that, another box or something. So again, let's use a Shiny module for that. We will need to use this filtering function. So we can filter. We can have a checkbox for that. So let's import a checkbox. Let's create a checkbox. Okay. We need one more thing. We need options for those, for this checkbox. So we could pull it from the data set. But again, for the simplicity, I will just create a simple vector. As you can see, there is no problem with exporting a vector from a box module. I will simply use it to populate the checkbox input and to use it to start with all selected species. Okay. And we have the UI. We need to do something to happen in the server part. So for sure the server will need to get some data. And it should produce a filtered data set. So let's use our new function. And let's use the, what is in the input. Okay. That should work. And now we need to add it to our main module. So we need, we need these filters to be imported. Be helpful here. And obviously we need a server part. Now this, we need to somehow assign the outcome from this server to be just this reactive into a variable and pass it to the table and the server. So let me quickly do it. Okay. So now what's going to happen is we should be able to see a checkbox input with filters, which will be passed to, and which will be controlling the data set and data set after filtering should be pushed to table and chart. Okay. So it almost worked. We again missed the reactive part. Here we have reactive filters, also reactive. Okay. Let's give it. I guess this is something we've done. Okay. Because it's, it's a reactive to do this. Okay. So we have a filtering and it should work. It works. And probably we also need to change a little bit this layout. So let's go to our styles. Let's make the grid three colon one. Again, to build. There is a way to not have to do it every time. Build stars has an argument watch. If you run it with that, it will be running constantly. And each time you change your CSS file, it will rebuild it and create a new one. But I'm not going to do this here. And same as with the previous features, this only works with the node version. This works. So this is not bad. It works. Maybe this could be wider, but let's leave it as this. And I will quickly add things to the repository so you can, you can have it. Okay. So another good practice in software development is to write tests, obviously. So in R and shiny, then obviously you have a very good framework for testing, test data, very nice package. So Rhino is not reinventing the wheel. It just helps you with writing your tests. So if you go to the directory, you already have a test directory and test that. There is a single file that tests the main server. We already modified that. So let's get rid of it. Let's write a new test. A few seconds before, we created a function for filtering. So let's create a file that will be responsible for testing that logic. It needs to start with test. What we need? We need to test that. So here, as you can see, I simply imported everything from test.dat. And I wouldn't do that in the application, but in the unit tests. And for the sake of the speed, I will leave that as it is. Let's import our function. And one thing you should notice here is that we are now in tests.dat directory. But we still start our path from the root. So this is something that, again, Rhino gives you. And it is hidden here. So when you start your R process, our profile is obviously red. And it will set the box path, which is the root where the box packet will look for modules to your working directory. That's why you don't have to do weird things like that here. Again, it will understand when we're... It understands such things, but there's no need to do that. Okay, we have not much time, so let me quickly copy the unit test. It's very simple. It just takes a mock data frame and checks if we filter by A, it will produce the proper IDs. And again, Rhino has a wrapper for tests. And okay, it just found the test that we wrote and all are working. Okay, but this is pretty common. What is not so common is the usage of end-to-end tests. So Rhino comes with a framework called Cypress, which is responsible for running a front end test, which is responsible for testing the front end of the application. So let me quickly run the basic tests. Okay, maybe I should run it differently. So it will... What it is now doing, it is running a Hedl's browser with the application and it checks if that worked. Okay, our application worked. And but to show you how does it actually work, I will quickly copy paste an actual end-to-end test. Okay, what this is going to do, this is going to open the application and this is going to click all the checkboxes. So it will expect some kind of message, which is not there. And let me show what happened, but this time with the interactive mode. So we will see what is going on. Okay, so we have our test, our test file, which I just edited and here's two tests. First it checks if the application starts and then it fails. That's okay. You see what happened? If all the checkboxes are unchecked, there is a problem because there is no data, so the chart fails. And you can go through the scenario of the script and check where things happen. We don't have time to dig into that, so this is a powerful tool. It is kind of competitive to what was recently shown on Studio Conf and on epsilon Shiny Conf and I mean Shiny Test 2. It is kind of a different, it requires node, it requires writing tests in JavaScript. So this is a sample test. But on the other hand, it is better comparing to Shiny Test 2 because it actually simulates the actions in the application. Shiny Test 2 does things like changing the value of the input and here we have an actual click on the input, so an actual event. So there are some differences. Probably right now in the future we'll also incorporate Shiny Test 2, so stay tuned. Is there a gallery of apps that use Rhino? Not yet. Some of our recent demos are using Rhino. So in general from looking at last few months, we in epsilon started using Rhino, so probably this will come in the future. For now, unfortunately, there is no gallery. The UI of the application will be similar. There is no difference in what you get. There is difference in how you write your application code. So don't expect from such a gallery anything different that you can see in some top-notch Shiny applications over the world that the key is what's going inside. Okay, we have only seven minutes, so maybe I'll end right now and I'll let you ask some questions. So from now you should be able to unmute yourself. So if you have any questions, if something was unclear, please ask. Now you can always write on the chat. If there are no questions, I can show you one more thing that is available in Rhino, which is logging. So if you go to that config file, which again uses a config package from from RStudio, so it utilizes things like having different environments and like that. Check the documentation. It's really, really cool. It is populated with two values, with Rhino log level and Rhino log file. So if you go to, let's use it here, we can start adding some messages, which are kind of a better than a simple print. Let me show you what's happening. In Rhino, you can import log, which will include seven standard levels of logs that can be produced by your application. And if I now run the application and start filtering, we'll see that it informs you that, okay, there was filtering action, this timestamp, and the level of the message is info. So it is a pretty standard in software engineering. The cool thing now is that you can change the level of those filters, of those messages. So if I change the value of the of the environmental variable, just here, to something else like warning, the patient will no longer produce those things. So if this is a production application, you can have a very limited logs. But if something happens, you can change the level of logging to debug and have very, very large logs to debug it very efficiently. So this is a small thing, but a really cool, really standard thing in the applications world. So this is now incorporated into Rhino without any additional configuration requirements and things like that. It just works. Okay. Time is up. Time's up. I think we can finish. If there is anything you want to ask, we probably have still time for a small question. If not, please, you can reach me on LinkedIn or on Twitter, or you can write to us at epsilon via our website. So thank you very much. Thank you for watching. How easy it is to use crosstalk with Rhino. It should work. In general, the most of the shiny packages in simply work, there are some problems with, it can be some problems with, for example, the debugger in our studio because of how the box is loading things. But in general, it should work. If it does not, then Rhino is pretty fresh. So any feedback and any comments will be appreciated. We will try to make it work if there are some problems. Okay. I don't see any more questions. So again, thank you very much. It's a great pleasure to be here with you. I hope that you will give Rhino a chance. Thank you.