Intro to Vue.js. Testing on kubernetes - rf-service frontend
In this article I'm showing my first steps in Vue.js by adding simple frontend to the existing rf-service.
Vue.js is getting more and more popularity, it is still behind React.js and Angular but catching up. It is known for its clarity and flexibility, that is why it makes a perfect opportunity to learn for frontend noobs such as myself.
This article is a part of series connected with testing on Kubernetes. It is not mandatory to go through previous articles, but it gives a better understanding of what I’m actually trying to achieve here. The Vue.js intro part is written independently so that with below short description of each of the previous parts you should be fine with going through it.
Instead of repeating same story of what Vue.js is, I would rather focus why it seemed perfect for me - knowing only basics of HTML, js and CSS. Vue.js is said to leverage best parts from React and Angular, making it also more lightweight and easy to learn. This was most important for me so that I can learn quickly and also not spend time on something that is far from the main players. You can find more detailed comparison created by Vue.js core team - Comparison with Other Frameworks.
Vue.js is also in upward trend which makes it perfect competence investment. On top of that you can expect many other people facing similar problems while learning it so access to information and learning should not be a problem. More on that in Why Is Vue.js a Front-end Trend of 2020?.
The third point that really caught my attention is that you can write mobile apps using Vue Native. I didn’t tried it yet, but possibility to implement some cool projects on your mobile sounds very tempting.
The recommended method for installation is through npm. If you don’t have it yet, this is the prerequisite for further steps. You can install Vue with below command:
Installing vue-cli makes a lot of things easier, so I used it also. You can add it with:
You can create your first project by executing:
This will create everything you need to see Vue.js in action including structure and example project that can be base for you changes as in my case. To see the UI just go to project folder and start the development server with:
You should see Hello world app on http://localhost:8080/.
Knowing above let’s just track how actually Vue.js code is being placed in final HTML document. Before that, we can already switch to the actual code of rf-service frontend. Just clone the repo from devopsspiral/rf-service-fe. Again it is vue project created with vue cli.
We will follow top-down path. If you go to public/index.html, you will see how the Vue.js app is mounted.
This is the place where src/App.vue is referenced. In App.vue there is template and style part, so you just literally create parts of your app as self-contained source of HTML and CSS. This looks really simple and is easy to manage.
Creating .vue files with template, style and js code is not the only way for templating used in Vue. At some point you can find that it is not enough for all the cases, you can find more info here.
I will explain routers later on, for now we can just assume that <router-link> is just a link to other parts of our application. Those can be found in src/views and src/components. There is not much difference of how you create views and components, they are kept separate just for clarity. They serve different functions though. Views are used to present parts of your app to the user, while components could be some internal machinery of your app referenced in views.
In my case Runner.vue, Result.vue and Config.vue are kept as views in src/views directory. If you look inside those files, you will see that apart from template and style, we have now extra vue/js logic that makes the whole thing dynamic.
I will just focus on Runner.vue as other views are pretty similar. The template part is simple, we have two components: button and test-list. The first one is just regular HTML button but with vue attribute v-on:click, that is pointing into function in script part in method section. This is how you define action from JS to be linked with HTML object. Just to better understand what is done there I’m performing a POST http call to rf-service api using Axios.
The second object in template is test-list which is as component in src/components/TestList.vue. As you can see Vue components are not only modules in Vue.js code, they become actual HTML tags that can be used in templates. Test-list is a bit more interesting because it implements test tree structure using recursion. If you look into its definition it uses recursive call in template section.
Other than that, there is @click which is shorthand for v-on:click and :style which is shorthand for v-bind:style - this is probably the most used vue directive. It binds the value of the parameter with variable or function in JS and this is where whole reactive magic happens. You don’t need to care about updating anything, whatever changes in JS is reflected in HTML.
You can also find v-if and v-for directives that basically works similar to what can be found in other templating engines (like jinja) and represents conditional rendering and loops. In this case v-if will conditionally render <div>, which is by the way boundary of v-if - you don’t use anything similar to endif for example. On the other side v-for will multiply test-list tags for each child in children.
Going to the script section, there are couple of distinct parts there. name and props is defining test-list representation as (HTML) component with parameters. data() is the place for all variables available across whole file. Just remember to add this. if you are referencing this variable inside script section. And lastly, we have computed and methods section. You may wander what is the difference if all in all they are both functions. The difference is in how they are used. indent() is actually used as value not action as in toggleChildren() case. This is why Vue needs to take some extra care of this value in a sense of reactivity i.e. compute the new value whenever this.indentation changes.
In Runner.vue there is another interesting section called created which is executed on page loading. In this case, it is another call to api to fetch some parameters metadata.
We did a little round tour of Vue elements and we’re back to the point where we would like to put things together. This is where routers come into play. As in other programming languages routers directs the flow into classes or methods, in Vue.js those will be components. If you have your app already created without router, don’t worry you can always add it using vue cli.
This will create necessary files in your project and you will be ready to use it. The actual routing paths are kept in src/router/index.js in routes variable. This is where you link paths with components.
Having the paths you can now link to them as it was done in App.vue.
All right, we have our app running well in development, what if we would like to actually share it with others as a dockerized service? This couldn’t be simpler, just use Dockerfile in the repo (taken from https://vuejs.org/v2/cookbook/dockerize-vuejs-app.html). It doesn’t have any app specific parts so it can be used as a base for whatever app you are building.
Getting back to broader context of this article, rf-service got api and frontend. It is still possible to run tests from cron job, but the results can be viewed in Results tab in rf-service-fe. Switch between modes is done by passing .Values.config helm parameter with configuration of fetcher and publisher as before. By default it is defined as empty string and all the configuration is done using Vue.js frontend.
As a result we have now 3 containers running: frontend with Vue.js app, rf-service and its api and Caddy serving as a store for Robot Framework results.
All the paths have explicit /api/ prefix for simplicity, this could be replaced by Flask blueprints or simply by mounting the app on specific path. Api is also missing swagger docs, which might be covered in future.
The app in final container is served by gevent which allowed to expose api without extra non-python dependencies and allowed easy implementation of backward compatibility.
As a result of having 3 containers in place. I decided to put them into one pod, this resulted in multiple ports in one service
and ingress. Expose over ingress became mandatory to allows easy handling on frontend side with paths /caddy/ and /api/. This is exactly why rf-service api needs to have /api/ prefix. Caddy /caddy/ path is handled by using rewrite (see .Values.caddy.setup and snippet below).
You can test the whole setup by first installing the chart on your k8s cluster (k3d in my case):
and then setting the example config:
I’m using localhost:8090 only because in this setup all the containers are in same pod and accessible via localhost on different ports. CaddyPublisher address is used by rf-srvice not external client, like browser.
The created frontend doesn’t have styling and still needs some more work, but building it with Vue.js was real fun. After getting the whole idea of components it was quite easy to add new stuff there. Solutions to most of my initial problems could be found in great Vue.js materials or on forums. Vue.js is really great tool for creating both simple and complex apps and it is definitely a lot of learning ahead of me in this matter.