Universal Control Plane
Docker Entreprise Edition
Docker Universal Control Plane (UCP) is the enterprise-grade cluster management tool from Docker. It lets you install it on-premises or in a private cloud. It aims to enable enterprises to migrate towards using container-based applications but also to manage them and monitor them through a single interface. I've been working with the UCP team since 2017 and participated in the major releases of UCP 2.2, 3.0, and 3.1. My role mainly focused on the user interface so for the time I've worked on this project I was a frontend engineer.
The video above showcases a detail view of one Kubernetes workload (here a Pod). The challenge when implementing such a screen in the product is to have a 1:1 mapping with what the original command-line interface displays for similar actions. Building accessible and scalable React components that can display the various data structures of a Kubernetes workload, or even a Swarm workload, in a cohesive manner is a priority as a frontend engineer on UCP.
The creation wizard of "Grants" is one example of a user flow I had to focus on with both a product manager and a designer. Building a form that can guide the users through the creation of a grant (a UCP resource that will give certain rights for a given user or team against a set of resource) was a challenge as it requires the user to choose between multiple elements such as a user, a team, a role, and a resource set in a certain order. The resulting components and forms with the transition we can see above are an example of UI engineering, UX and product work I've got to take part on a daily basis as part of the UCP team.
This is perhaps one of my favorite features I got to work on. Displaying large sets of data in tables is central to UCP. I worked here on adding an extra small UI/UX feature to allow the user to resize the columns of the tables and have them stick to their size through navigation by using Redux.
For this project I focused exclusively on the frontend with the following technology:
UCP was the first enterprise software I've worked on as I've only focused on SaaS before. It was a real change compared to the work I had done before since we only had 2 releases per year, and our user target was big corporations which meant that there was little to no margin of error when it came to releasing new features. This is how I've got to grow my skills as a frontend engineer. For the first time, I had to mainly focus on testing not as a nice to have but as a necessity. I got to write tests for the UCP UI at every level:
Unit tests: we used Jest for our unit tests which are perhaps among my favorite tools. Unit testing was essentially done at the component level, for reducers, and for utility functions at the beginning. I've seen the benefits of proper unit testing in previous projects but UCP is perhaps the first project where I learned how to write proper tests (not testing implementation details, judge whether a test is valuable or not, ...)
Integration tests: this is the part that I've felt was the most beneficial in terms of testing for this project. Using some pre-existing tools and re-implementing some mocked versions of some of the building blocks of our UI helped to efficiently write advanced tests with mocked data to check specific states or test the flow of some specific scenarios. Among these tests, I wrote some tooling for my team to have a more advanced "mockedStore" as well as a full mock of the UCP UI SDKs to test action creators and Redux aware views of our app. It was fun and made testing a fun task. You can see a somehow similar implementation of this utility here: https://gist.github.com/MaximeHeckel/e640030e305c5ae9d38f133e1c032631. By adding and extra middleware that pushes every action executed in an array, we were able to:
- fully test complex Redux actions and how they influence the state of the app.
- test entire user flows like filling a form and checking if the submit API call contained the right payload and triggered the correct Redux action against the correct API endpoint.
As a result, every reducer, action creators and form in the UI had some sort of test that would guarantee them to work under the right circumstances.
- E2E tests: this was perhaps the most challenging part of the testing stack I worked with. Indeed, the toughest part was to ensure that the test we've written would always act in the same way and avoid any random failures which at the beginning felt like an impossible task given the complexity of the UCP infrastructure, but in the end, we made sure that every essential part of the app was fully covered by e2e tests. E2E testing was based on Nightwatch and Selenium.
I also learned how to appreciate good tooling while working on this project. Both static typing with ESLint and type checking with Flow helped me to ensure consistency in the codebase, fix tech debt and avoid mistakes to happen before they hit production. Prettier is also among these tools that were tremendously helpful.
As stated in the integration testing part, I really enjoy building nice tooling, but what I enjoyed perhaps the most here was to build very efficient and useful components. The sub-component (or compound components) pattern (see below) was perhaps one of my favorite component patterns.
The tools and method used on this project inspired me to write about them on medium:
This is the first large scale project I got to work on. UCP contains more than 500 views, forms and lists and more than 100 different React components. Ensuring performance and scalability for all these views and components was definitely a priority and one of the biggest challenges. I think the following best describes the challenges UI engineers face on a daily basis with big projects:
In a tiny app, we can hardcode a lot of special cases to account for the above problems. But apps tend to grow. We want to be able to reuse, fork, and join parts of our code, and work on it collectively. We want to define clear boundaries between the pieces familiar to different people, and avoid making often-changing logic too rigid. How do we create abstractions that hide implementation details of a particular UI part? How do we avoid re-introducing the same problems that we just solved as our app grows? — Quote from The Elements of UI Engineering
There are only 2 big releases per year. Having good automation and test coverage was key to make sure no issues were pushed to productions. If any error made it to the final release, the customer had to wait for an entire month to get a patch.
Given its size, it was easy for this UI project to carry a lot of tech debt. Addressing it was even more complicated due to the bi-annual release cycle, there was little to no margin for error when replacing or updating code.
UCP supports both Swarm and Kubernetes. These 2 orchestrators have different APIs, but the frontend engineering team had to provide the same experience for both of them.