<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Wrike Tech Club</title>
    <description>The latest articles on Forem by Wrike Tech Club (@wriketechclub).</description>
    <link>https://forem.com/wriketechclub</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F231126%2Fb172dc3f-eaf9-4345-a650-764843d42b92.png</url>
      <title>Forem: Wrike Tech Club</title>
      <link>https://forem.com/wriketechclub</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/wriketechclub"/>
    <language>en</language>
    <item>
      <title>Callisto: An Easy Way To Run Selenium Tests in the Cloud</title>
      <dc:creator>Wrike Tech Club</dc:creator>
      <pubDate>Tue, 09 Feb 2021 11:35:52 +0000</pubDate>
      <link>https://forem.com/wriketechclub/callisto-an-easy-way-to-run-selenium-tests-in-the-cloud-268</link>
      <guid>https://forem.com/wriketechclub/callisto-an-easy-way-to-run-selenium-tests-in-the-cloud-268</guid>
      <description>&lt;p&gt;Wrike recently open sourced a tool for Selenium testing called &lt;strong&gt;Callisto&lt;/strong&gt;, so we wanted to share our experience with its development and usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Say no to Selenium Grid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/wrike/callisto"&gt;Callisto&lt;/a&gt; is an open-source Kubernetes-native implementation of Selenium Grid.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We had previously been using Selenium Grid, but the experience wasn’t always great.&lt;/p&gt;

&lt;p&gt;Selenium Grid has critical downsides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;It demands a lot of computational resources, but there’s always a chance of stability and reliability issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Selenium Hub (the part of Grid) can be an infrastructure bottleneck; it stores sessions/nodes mapping and forwards requests from a test to an appropriate node. It’s not possible to horizontally scale Hub by adding new instances, so if Hub is down, all sessions in Grid are inaccessible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After a while we had a broad range of Selenium configurations (nodes, browsers, etc.), and it was difficult to maintain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There were constant pain points of observability and error handling. Timeouts and unpredictable delays of test execution happened often, and unfortunately we had no tools to debug these issues properly.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It didn’t look good so we started to think about how we could fix these issues. We wanted the platform to run tests and not require constant support from QA and operation engineers.&lt;/p&gt;

&lt;p&gt;As a result of our initial research we learned what we wanted our infrastructure to be: it should be managed by the Infrastructure as Code approach, &lt;a href="https://sre.google/sre-book/eliminating-toil/"&gt;eliminate toil and manual work&lt;/a&gt;, and bring the benefits of immutability and autoscaling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key application requirements and Kubernetes (unsurprisingly)
&lt;/h2&gt;

&lt;p&gt;Based on previous conclusions we defined some key application requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;We don’t want to manage data and think about storage. That’s why our solution for Selenium testing should be stateless.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It should be lightweight so we can optimise our resource usage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Containers and orchestration are cool for short release cycles and fast iterative development, so we started to think about the Kubernetes-native approach.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since we wanted to increase visibility, we decided to use a fresh and clean environment for each and every test.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In regards to the second point above, it’s easy to understand why we chose Kubernetes, as there are obvious perks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Kubernetes is an open-source solution, and has a great community and ecosystem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It allows you to build an on-demand and scalable infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The powerful API is helpful for any integration purposes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, we can declare all objects as code and pass it through our CI/CD pipelines without any trouble.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Our failed attempt to not reinvent the wheel
&lt;/h2&gt;

&lt;p&gt;After we described what we wanted to achieve, we researched existing solutions.&lt;/p&gt;

&lt;p&gt;It’s no surprise that we started with considering to fork Selenium Grid.&lt;/p&gt;

&lt;p&gt;But we soon realized the Selenium codebase is big and complex. If we wanted to develop and maintain it, we needed to hire people with specific and specialized skill sets. And still it’d be difficult to integrate our changes to upstream.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6hLb-ycL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0ikpy5aw57sygbvy7st4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6hLb-ycL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/0ikpy5aw57sygbvy7st4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At that time there was the public beta of &lt;a href="https://aerokube.com/moon/"&gt;Moon&lt;/a&gt; from Aerokube. Moon is the solution that brings you one universal autotest endpoint for all your applications and teams. Look’s good, but you have to pay to use it.&lt;/p&gt;

&lt;p&gt;Also we understood that if we simplify this approach we’ll get a scalable, stateless solution that could be built quickly and on your own.&lt;/p&gt;

&lt;p&gt;There’s no need for one central endpoint for all tests if you’re able to create several test endpoints with various configurations for every application on the fly. It’s simple enough to set up all the parameters for a test run and an infrastructure from the continuous integration pipeline.&lt;/p&gt;

&lt;p&gt;So let’s conclude our intentions. We don’t want to develop a multi-purpose and complex toolchain. &lt;strong&gt;Instead, we decided to create a Kubernetes-native and simple solution that &lt;a href="https://en.wikipedia.org/wiki/Unix_philosophy"&gt;should do one thing and do it well&lt;/a&gt;.&lt;/strong&gt; And as long as we keep it simple, we can observe and debug it with less effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep it simple
&lt;/h2&gt;

&lt;p&gt;Before we dive into a detailed review of Callisto, we should outline what problem it solves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generally, there are UI tests that require browsers to execute some actions.&lt;/strong&gt; To create a pod with a browser, we have to use the Kubernetes API. In principle, the tests could do it themselves, but this way has drawbacks that include observability and security issues. Another option is to move the Kubernetes interaction logic to a specific service.&lt;/p&gt;

&lt;p&gt;That’s why we’ve created Callisto.&lt;/p&gt;

&lt;p&gt;The test asks Callisto to create a browser. Using Kubernetes API, Callisto creates a pod with Webdriver and a browser, gives it to a test, and then deletes a pod at the end of a test.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Callisto works
&lt;/h2&gt;

&lt;p&gt;To deploy Callisto you can use the production-ready &lt;a href="https://github.com/wrike/callisto-chart"&gt;Helm chart&lt;/a&gt;; to automate the deployment, just use your usual CI/CD toolchain. We prefer to deploy the Callisto instance for every CI pipeline and shut it down at the end of test runs.&lt;/p&gt;

&lt;p&gt;Let’s take a closer look at the usual browser creation workflow. This workflow is simplified and doesn’t include Kubernetes-related parts (ingress, service, etc).&lt;/p&gt;

&lt;p&gt;Some external clients (e.g., Java-based autotests) send POST requests to Callisto endpoint for browser creation purposes. Callisto uses standard &lt;a href="https://www.w3.org/TR/webdriver/"&gt;Webdriver&lt;/a&gt; protocol. Client creates a session by sending a POST to &lt;em&gt;/session&lt;/em&gt; &lt;a href="https://www.w3.org/TR/webdriver/#new-session"&gt;endpoint&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sK_L6p9R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n6x30kydwpynk8454hdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sK_L6p9R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n6x30kydwpynk8454hdv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The request is first handled by Nginx, which then forwards &lt;em&gt;/session&lt;/em&gt; requests to the Callisto application. Callisto processes these requests and interacts with the Kubernetes API to create a Webdriver pod.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--x0D37hvL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5e63638apsxgx96zew3b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--x0D37hvL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5e63638apsxgx96zew3b.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the pod is ready, Callisto passes &lt;em&gt;/session&lt;/em&gt; request to the Webdriver instance inside the pod, and Webdriver creates a browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yYI98ALT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/azzxozpzfjsp7nyle05s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yYI98ALT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/azzxozpzfjsp7nyle05s.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the browser is created, Callisto returns &lt;strong&gt;SessionId&lt;/strong&gt; to a client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BpFc_mX4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/eh1semsrd7pwynpl3fx7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BpFc_mX4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/eh1semsrd7pwynpl3fx7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SessionId&lt;/strong&gt; contains information about a pod name and internal IP. Nginx takes it into account and forwards requests from clients (tests) to appropriate pods with Webdriver and browser. There’s no need to store session data in the database, and that’s why Callisto is a stateless and fault tolerant solution.&lt;/p&gt;

&lt;p&gt;Here you can see the complete workflow that we use to create a Webdriver session. After this stage Webdriver and the browser inside the cluster are ready to work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9mOVRfnx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/er8nkhf75xdd6zsny10z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9mOVRfnx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/er8nkhf75xdd6zsny10z.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The client can interact with Webdriver using SessionID from the previous step and sends commands to the browser. Nginx gets this type of request and forwards it to Webdriver, then a browser performs some test actions and returns the result to the client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kqbo-HJn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/s9rlriwlrfvrcdod21xn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kqbo-HJn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/s9rlriwlrfvrcdod21xn.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the job is finally done, a client needs to clean up the mess. Our client sends another request to Callisto endpoint, in this case it should be &lt;a href="https://www.w3.org/TR/webdriver/#delete-session"&gt;DELETE http request&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Efq5rIYg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gui1jypr9dibsnu2f27d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Efq5rIYg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gui1jypr9dibsnu2f27d.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Usually after a DELETE request, Webdriver uses the WebDriver.quit() method to close all browser windows and terminate the session. But with Callisto you don’t have to waste time on a browser termination, because Callisto creates a brand new pod for every test. So we can skip termination and delete a pod right away, allowing us to complete a test as fast as possible and decrease the cost of infrastructure on a large scale.&lt;/p&gt;

&lt;p&gt;That’s why Callisto just deletes the Webdriver pod using Kubernetes API and returns a WebDriver.quit() sample response to a client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4yFgpl8G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gzlwohidko18u7ylyvvv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4yFgpl8G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gzlwohidko18u7ylyvvv.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s a complete workflow of Webdriver sessions termination.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1kqHNoxo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5xsyryik9pg13xfsllu1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1kqHNoxo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5xsyryik9pg13xfsllu1.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you should give Callisto a try
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;It’s easy to deploy and use Callisto on top of your Kubernetes cluster.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Callisto is stateless and therefore reliable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The stateless and lightweight nature of Callisto makes it fault-tolerant and gives you the ability to run your tests on &lt;a href="https://blog.engineyard.com/pets-vs-cattle"&gt;cheap hardware&lt;/a&gt; without trade-offs between reliability and costs. You can run Callisto on top of &lt;a href="https://cloud.google.com/compute/docs/instances/preemptible"&gt;preemptible&lt;/a&gt; or &lt;a href="https://aws.amazon.com/ec2/spot/"&gt;spot&lt;/a&gt; instances. For example, we prefer to deploy Google Kubernetes Engine worker nodes based on preemptible instances, and our spendings decrease significantly compared to Selenium Grid usage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Callisto uses &lt;a href="https://github.com/aerokube/images"&gt;open-source Webdriver images&lt;/a&gt; (all thanks to folks from Aerokube), allowing you to focus on your tests instead of browser packaging and updates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Callisto provides a useful set of metrics (either for monitoring or debugging) in Prometheus format. You can build fancy dashboards in Grafana based on these metrics.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thanks for reading! I hope everything was clear and informative enough for you to start using Callisto. You can get it all on Github: &lt;a href="https://github.com/wrike/callisto"&gt;source code&lt;/a&gt;, &lt;a href="https://github.com/wrike/callisto/blob/master/README.md"&gt;docs&lt;/a&gt;, and &lt;a href="https://github.com/wrike/callisto-chart"&gt;Helm chart&lt;/a&gt;. Feel free to contribute and share your experience. Good luck!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>testing</category>
      <category>devops</category>
      <category>testdev</category>
    </item>
    <item>
      <title>Zones in Dart: Open Heart Surgery for Environment</title>
      <dc:creator>Wrike Tech Club</dc:creator>
      <pubDate>Fri, 23 Oct 2020 12:28:50 +0000</pubDate>
      <link>https://forem.com/wriketechclub/zones-in-dart-open-heart-surgery-for-environment-272j</link>
      <guid>https://forem.com/wriketechclub/zones-in-dart-open-heart-surgery-for-environment-272j</guid>
      <description>&lt;p&gt;Hello! My name is Dima, I’m a frontend developer at Wrike. We write the client part of the project in Dart, and we often deal with asynchronous operations. Zone is one of the most useful and powerful tools that Dart provides for this purpose. I decided to dig into it myself and share my knowledge with you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: All the code used in this article only pretends to be a copy paste. In fact, I’ve greatly simplified it and got rid of the irrelevant details. For this article, I used Dart version 2.7.2 and AngularDart version 5.0.0.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I hadn’t heard much about zones before Dart. They aren’t typically used directly and have quite specific features. As the &lt;code&gt;dart:async&lt;/code&gt; import hints to us, they make sense particularly for asynchronous operations.&lt;/p&gt;

&lt;p&gt;In Wrike (as well as any other project on AngularDart), you can usually find zones in pieces of code like this:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Advanced Dart developers call it ancestral knowledge — just do what others did before you and it’ll work.&lt;/p&gt;

&lt;p&gt;However, if you look under the hood of some libraries, you’ll find that zones are often used to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplify the API in utilities that use asynchronous operations.&lt;/li&gt;
&lt;li&gt;Fix some drops in app performance.&lt;/li&gt;
&lt;li&gt;Catch unhandled exceptions and fix bugs (which are sometimes caused by zones but are irrelevant now).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are a few resources about zones in the Dart community, except for the issues on GitHub. This is the &lt;a href="https://api.dart.dev/stable/2.10.2/index.html" rel="noopener noreferrer"&gt;API documentation&lt;/a&gt;, &lt;a href="https://dart.dev/articles/archive/zones" rel="noopener noreferrer"&gt;an article from the official site of the language&lt;/a&gt;, which explains the case of catching asynchronous errors, and a few &lt;a href="https://www.youtube.com/watch?v=QIEhkvYRVNg" rel="noopener noreferrer"&gt;talks&lt;/a&gt; such as the &lt;a href="https://dartup.ru/eng/" rel="noopener noreferrer"&gt;DartUP conference&lt;/a&gt;. Therefore, as the most complete guide I suggest the code itself.&lt;/p&gt;

&lt;p&gt;In this article I’ll show you code examples from the libraries that we use in the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;package:intl&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package:quiver&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package:angular&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start with the basics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intl and temporary locale switch
&lt;/h2&gt;

&lt;p&gt;The Intl package is part of our localization engine. The principle of operation is quite simple: When the application starts, we look for a locale selected by default, load it, and each time we call the message or plural methods, we return the required text for the passed key.&lt;/p&gt;

&lt;p&gt;Keys are named this way:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Sometimes we need to change the locale temporarily, i.e., a user enters a time period and we need to parse it from the current language. If we can’t do it from the current one, then we’ll try a fallback language. We use the &lt;code&gt;withLocale&lt;/code&gt; method provided for this purpose:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Here we’re trying to parse user input first in the default locale, and then in the fallback locale.&lt;/p&gt;

&lt;p&gt;It looks like &lt;code&gt;withLocale&lt;/code&gt; changes the current language to the specified one, executes the passed callback, and then returns everything as it was.&lt;/p&gt;

&lt;p&gt;But the &lt;code&gt;parseText&lt;/code&gt; method returns with Future, because the necessary locale may not be loaded yet, which means we need to wait for the result. While we’re waiting, the user touches something in the interface and it starts to re-render. Since the current locale at this moment is Russian, the interface also changed the language to Russian. And when the operation is over, it’s back to English. No good.&lt;/p&gt;

&lt;p&gt;What we want here is Future itself to report that the asynchronous operation has ended, then to pass on control and the result. In that case, the algorithm could switch the locale at the right time.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. What’s under the hood of Future?
&lt;/h3&gt;

&lt;p&gt;The good news is it’s possible! We’ll call ab asynchronous function and get a Future instance at the output:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;When creating an instance, Future remembers the current zone. Now we’ll need to plan the processing of the result by using the then method:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Interesting! First, we’ll tell the current zone which callback will be executed in the future. Then we’ll create a new Future, schedule it to process the result of the asynchronous operation, and return it. The new Future remembers the zone where it was created — &lt;code&gt;Zone.current&lt;/code&gt;. After getting the result thanks to the &lt;code&gt;runUnary&lt;/code&gt; method, it executes a callback in the saved zone. Zone knows when the callback was added and when it should be executed, which means the execution process can be managed!&lt;/p&gt;

&lt;h3&gt;
  
  
  2. What’s a zone, where does the current zone come from, and what does “run inside the zone” mean?
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;“It’s an execution context.” — Brian Ford, zone.js author&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Zone is an object that can be described by the word “context.” It can carry contextual data and behavior. All entities that know about zones use the data or behavior of the current zone in one way or another. When working with Future, we’ve already seen that if it needs to execute a callback, it delegates this work to the zone using the &lt;code&gt;run*&lt;/code&gt; family of methods. And it already imposes final features and side effects on the execution of the callback.&lt;/p&gt;

&lt;p&gt;The current zone is the zone currently referenced in the &lt;code&gt;_current&lt;/code&gt; field. &lt;code&gt;Zone.current&lt;/code&gt; is a static getter for accessing &lt;code&gt;_current&lt;/code&gt;. In the code it looks like this:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This field differs from a global variable because it can’t be easily changed. To do this, you need a set of &lt;code&gt;run*&lt;/code&gt; methods the zone instance has: &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;runUnary&lt;/code&gt;, and &lt;code&gt;runBinary&lt;/code&gt;. In the simplest case, these methods temporarily write their zone in the &lt;code&gt;_current&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Before executing the callback, a new zone is written in the &lt;code&gt;_current field&lt;/code&gt;, and the old zone is returned after execution. To execute inside the zone means replacing the zone in the &lt;code&gt;Zone.current&lt;/code&gt; field with the one selected for the duration of the function.&lt;/p&gt;

&lt;p&gt;That’s it! You can even say that if the current field had a setter, these two code pieces would act similarly:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;An important difference is that &lt;code&gt;run*&lt;/code&gt; methods guarantee the current zone will not only switch to the necessary one, but will also return to the previous one when the callback is finished. The switch will be only temporary. Instead, the developer may forget about switching or simply consider it unnecessary.&lt;/p&gt;

&lt;p&gt;Now you see that the Intl service should be fine if it uses zones under the hood. They’ll help control the switching of the locale.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. How does Intl service use zones?
&lt;/h3&gt;

&lt;p&gt;Let’s go back to the service and look at the &lt;code&gt;withLocale&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;That’s something new! Let’s start at the beginning.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;runZoned&lt;/code&gt; function executes a callback inside the zone. But we don’t provide an instance of the existing zone, because &lt;code&gt;runZoned&lt;/code&gt; creates a new zone and immediately executes a callback in it. And the &lt;code&gt;run*&lt;/code&gt; methods execute a callback in an already created zone.&lt;/p&gt;

&lt;p&gt;The second interesting detail here is the &lt;code&gt;zoneValues&lt;/code&gt; map — powerful tool that allows you to save data in the zone. You can put any type of data in &lt;code&gt;zoneValues&lt;/code&gt; with a unique key (our piece of code uses Symbol).&lt;/p&gt;

&lt;p&gt;Now let’s see where the data is read:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Voila! First, we’ll check if the locale ID is in the zone. Next, we’ll return either the locale ID or the default locale. If a locale is recorded in the zone, it’ll be used.&lt;/p&gt;

&lt;p&gt;Data in the zone is read using the &lt;code&gt;[]&lt;/code&gt; operator, which searches for the value in the zone and its parents (we’ll discuss them later). But the &lt;code&gt;[]=&lt;/code&gt; operator is undefined, which means that the data assigned to the zone during creation can’t be changed. This is why the &lt;code&gt;withLocale&lt;/code&gt; method uses &lt;code&gt;runZoned&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;A pre-created zone wouldn’t work here, so we’re constantly creating a new zone with the current data.&lt;/p&gt;

&lt;p&gt;Finally, let’s go back to the beginning:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Now we know the callback of the &lt;code&gt;withLocale&lt;/code&gt; method will be executed in the zone, and the locale we need will be written in it. Moreover, each Future created in this zone will save a link to the zone and execute its own callbacks in it. This means that the locale will switch to the specified one each time before executing &lt;code&gt;_parseText&lt;/code&gt; and return immediately after executing &lt;code&gt;_parseText&lt;/code&gt;. Just what the doctor ordered!&lt;/p&gt;

&lt;p&gt;Now we also know how Future can interact with zones. Future, Stream, and Timer are “soaked” with such interactions with zones inside and out. Zones are aware of almost every step they take; that’s why they have such capabilities and power. We can learn more about them from the following example.&lt;/p&gt;

&lt;h2&gt;
  
  
  FakeAsync and fast asynchronous code testing
&lt;/h2&gt;

&lt;p&gt;In our team, all frontend developers write unit tests. Sometimes we need to test code that works asynchronously. Dart out of the box has great testing tools, i.e., the test package that can work with asynchronous tests. When we need to wait, it’s enough to return Future from the callback of the test function, and the tests will wait for the call of the expect function until Future is completed:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In this fairytale, I’m not satisfied with one thing: waiting. What if we’re testing a stream with debounce per second or an operation has a one-hour timer? In such cases it’s worth using the mock from the outside, but this isn’t always possible.&lt;/p&gt;

&lt;p&gt;We’re back to the problem of asynchronous tasks. But now we know we can ask zones for help. Authors of &lt;code&gt;package:quiver&lt;/code&gt; have already done this and have written a FakeAsync utility.&lt;/p&gt;

&lt;p&gt;This is how it works:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We’ll create a FakeAsync object, an asynchronous operation will start with it, and all timers and microtasks will be reset. And then all the scheduled work is executed.&lt;/p&gt;

&lt;p&gt;Let’s imagine ourselves as Penn and Teller again and see how this magic works.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. How FakeAsync actually works
&lt;/h3&gt;

&lt;p&gt;Let’s take a look at the &lt;code&gt;run&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Create your own zone, run a callback in it, and return the result. There’s nothing to it.&lt;br&gt;
But from the first line we’ll meet two new details: fork and specification.&lt;/p&gt;

&lt;p&gt;When starting the Dart app, we always have one zone already: the root. It’s so special that you can always access it via the static getter &lt;code&gt;Zone.root&lt;/code&gt;. Each correct zone must be a root fork, since all the basic features are implemented in the root zone. Remember this &lt;code&gt;run&lt;/code&gt; piece that should switch the current zone?&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;That was a cheat similar to the old school rule that you can’t divide by zero. In fact, the code should look more like this:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;So under the guise of a normal zone, the real game changer was hidden!&lt;/p&gt;

&lt;p&gt;All other zones are forks of the root zone. You need them to add side effects to your main work.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. How ZoneSpecification affects the behavior of the zone
&lt;/h3&gt;

&lt;p&gt;ZoneSpecification is the second important public interface for zones, along with &lt;code&gt;zoneValues&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;These are all possible ways to influence the behavior of the zone and, consequently, the environment. Each of the specification getters is a handler function. For each handler the first three arguments are the same, so we can use the example of a single function to explore several.&lt;/p&gt;

&lt;p&gt;Let’s analyze a small theoretical example by calculating how many times the code will ask you to do something in the zone:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Here we create a zone with its own specification. All callbacks called using the &lt;code&gt;run&lt;/code&gt; method will pass through the &lt;code&gt;run&lt;/code&gt; handler. The handler side effect is an increment of the global counter, so we’ll calculate it.&lt;/p&gt;

&lt;p&gt;There are some points of interest here.&lt;/p&gt;

&lt;p&gt;When we call any method from a zone, it calls the corresponding handler from its specification under the hood. This method helps minimize the number of arguments that we pass to the handler function. It substitutes the first three.&lt;/p&gt;

&lt;p&gt;The forked zone is a node of the tree. When we call the zone method with some arguments, they must be “passed” from the node to the root through each parent. If the chain is broken, the work we’re counting on may not be completed, because nothing will reach the root zone. This makes sense for cutting off extra work, but not in our example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first argument&lt;/strong&gt; is the zone that owns the handler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The second argument&lt;/strong&gt; is the parent zone delegate, which is used for “passing” further. If we don’t call it, the root zone in the example won’t be able to switch the current zone to the one we need, and there’s no other way to write in the &lt;code&gt;_current&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The third argument&lt;/strong&gt; is the zone where the call initially happened. Sometimes we need to know it because the zone that owns the handler may not be the last in the hierarchy. When this argument reaches the root, the value of the current zone in the example will change to it.&lt;/p&gt;

&lt;p&gt;This sounds complicated, but it usually comes with practice. Another small example is illustrated by a diagram:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuwpq9vgtvee2cxhg024c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuwpq9vgtvee2cxhg024c.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Here we have a hypothetical hierarchy: You can see step by step what happens if you execute your work in zone B while the current zone is D.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first arguments for all other handlers work the same way, which means the same patterns apply to them.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. What side effects needs FakeAsync
&lt;/h3&gt;

&lt;p&gt;Now back to FakeAsync. As we recall, in the run method the current zone is forked, and a new one is created using the specification. Let’s take a look:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;First we see the &lt;code&gt;scheduleMicrotask&lt;/code&gt; handler. It’ll be called when something asks to schedule work in a microtask so it runs immediately after the current execution thread. For example, Future is required to process the result in a microtask, so every single Future will ask the zone to do this at least once. And not only Future — asynchronous Stream also actively uses it.&lt;/p&gt;

&lt;p&gt;It’s an easy way FakeAsync schedules microtasks — they’re stored synchronously under the hood for later, without any questions.&lt;/p&gt;

&lt;p&gt;Next is the &lt;code&gt;createTimer&lt;/code&gt; handler. You can call the &lt;code&gt;createTimer&lt;/code&gt; method for a zone to get a Timer object, oddly enough. You might be asking, “What about its own constructor?” Let’s take a look:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The Dart timer is just a kind of wrapper over the environment timer, but the zone wants to be able to influence the process of creating it, i.e., if you need to create a dummy timer that doesn’t count anything. This is exactly what FakeAsync does: It creates a &lt;code&gt;_FakeTimer&lt;/code&gt;, and the only task is to save the callback passed to it.&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Unlike our example with the &lt;code&gt;run&lt;/code&gt; handler, the parent delegate isn’t used here because we don’t really need to schedule anything. The chain is broken, so there will be no timers or microtasks in the test environment. In fact, the point of the FakeAsync zone is to collect all callbacks for timers and microtasks that are scheduled by any asynchronous structures or actions during code execution.&lt;/p&gt;

&lt;p&gt;All of this is done in order to then execute them synchronously in the right order! This can happen by calling the &lt;code&gt;flushTimers&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The method iterates through the scheduled timers, synchronously calls their callbacks, and then synchronously iterates through all their scheduled microtasks. It does this until there are no tasks left. As a result, all asynchronous tasks are executed synchronously!&lt;/p&gt;

&lt;p&gt;We learned how zones are created and talked about ZoneSpecification, but there are still many handlers not mentioned.&lt;/p&gt;

&lt;p&gt;This is what they can also do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Catch and handle errors (&lt;code&gt;handleUncaughtError&lt;/code&gt;, &lt;code&gt;errorCallback&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Modify the callback at the creation stage (&lt;code&gt;registerCallback*&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create a repeating timer (&lt;code&gt;createPeriodicTimer&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Affect standard logging (&lt;code&gt;print&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Influence the zone fork process (&lt;code&gt;fork&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it for today. In this article we looked at examples of using zones in two small libraries and hopefully understood their basic features. In the next article, we’ll analyze details that aren’t self-evident on AngularDart — a framework that has slotted into our frontend life more than others.&lt;/p&gt;

&lt;p&gt;Feel free to leave a comment below with your thoughts. I’d be more than happy to answer your questions!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>dart</category>
      <category>dartlang</category>
    </item>
    <item>
      <title>API Portal: What to Consider When Designing </title>
      <dc:creator>Wrike Tech Club</dc:creator>
      <pubDate>Fri, 14 Aug 2020 16:09:06 +0000</pubDate>
      <link>https://forem.com/wriketechclub/api-portal-what-to-consider-when-designing-2c5</link>
      <guid>https://forem.com/wriketechclub/api-portal-what-to-consider-when-designing-2c5</guid>
      <description>&lt;h1&gt;
  
  
  Wrike's experience
&lt;/h1&gt;

&lt;p&gt;Using public APIs helps companies to increase the value of their own resources, create unique content, and meet the requirements of various business tasks. Wrike is no exception. More than 30,000 applications have been created based on the Wrike API. The number of product users is growing, which means that the requirements for the portal are getting higher every day.&lt;/p&gt;

&lt;p&gt;In this article, I share my experiences with redesigning the Wrike developer portal interface and show you some changes worth noting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;During the redesign we managed to:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create intuitive navigation.&lt;/li&gt;
&lt;li&gt;Create a grid for optimal content display on both desktop and mobile devices.&lt;/li&gt;
&lt;li&gt;Upgrade the portal design according to the current design system and create the missing components.&lt;/li&gt;
&lt;li&gt;Reach the AA level of &lt;a href="https://www.w3.org/WAI/standards-guidelines/"&gt;accessibility standards&lt;/a&gt; and implement them.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Intuitive navigation
&lt;/h2&gt;

&lt;p&gt;We conducted a series of interviews with the main users of the portal — backend developers and the user support team, who gave us feedback from external users.&lt;/p&gt;

&lt;p&gt;We learned that, in addition to technical problems with notifying and updating information, the portal had navigation problems: It was difficult for users to find important information such as references and documentation. It was also difficult to get to the sections located after the methods. For example, to find the BI Export section, you had to view all the methods first, and there was no option to collapse the section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wGDyTT2R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4vguk63z4cfdd9pnosa1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wGDyTT2R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4vguk63z4cfdd9pnosa1.png" alt="Alt Text"&gt;&lt;/a&gt;Navigation on the previous version of the portal&lt;br&gt;
&lt;br&gt; &lt;/p&gt;

&lt;p&gt;To optimize the portal structure, we highlighted the main sections and subsections, as well as separated the technical content from informational. In the new portal, we set five main sections: Introduction, API Documentation, API Reference, BI Export, and Support. The Community API section is a separate link at the bottom of the sidebar so that the user can quickly get help.&lt;/p&gt;

&lt;p&gt;Placing the navigation in the sidebar helps users see not only the main sections but also the subsections. And the ability to hide methods makes it easier to work with the panel. If needed, the user can collapse unnecessary sections or go deeper into the ones they need. The logo, toggle, and search field are still in the top menu (under development).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---Z4PDKDe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ksf1f9ybgxsnb9huliuv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Z4PDKDe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ksf1f9ybgxsnb9huliuv.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;Portal navigation redesign (before and after)&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;To simplify the interaction for the user, it’s better to show the structure of the portal with a level of nesting no deeper than two. Additional features (such as search) can be added to the top menu. If the user needs to log in to use the portal, you can also separate the login button from the main navigation. This way you can provide a quick search for the necessary information and easy navigation between pages.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JNBg4Dil--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l13jjjkgktgj7buhm6ye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JNBg4Dil--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/l13jjjkgktgj7buhm6ye.png" alt="Alt Text"&gt;&lt;/a&gt;Navigation on the new portal&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Grid for optimal content display
&lt;/h2&gt;

&lt;p&gt;The Wrike developer portal consists mainly of static pages with varied information content. Tables and text blocks take up the full width of the grid, so the number of columns doesn’t matter.&lt;/p&gt;

&lt;p&gt;We paid special attention to the methods page. There are two ways to build this page:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Divide the content part into &lt;a href="https://developers.asana.com/docs/custom-field-settings"&gt;two blocks&lt;/a&gt;: the description and tables on the left, and examples on the right.&lt;/li&gt;
&lt;li&gt;Make a &lt;a href="https://goto-developer.logmeininc.com/content/gotowebinar-api-reference-v2#/Webinars/getAllAccountWebinars"&gt;single block&lt;/a&gt; with content: examples are inside each method.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The previous version of the portal used the first method.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--63Ajqf40--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vvmjr8kt97e0q6gomjuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--63Ajqf40--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/vvmjr8kt97e0q6gomjuk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;Method page on the previous version of the portal: description and query on the left, the slider with the response and examples on the right&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;We analyzed the Wrike API methods and decided to use the second option. It allows us to keep tables with their nesting in an easy-to-read form, as well as view the response and example without switching the slider.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hDREoKKm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ylymvnifmicd9gtlyz5u.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hDREoKKm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ylymvnifmicd9gtlyz5u.jpeg" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;Method page on the new portal&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;It’s important to predict the grid behavior at different resolutions, so that the portal is correctly displayed from the screen of a computer, laptop, or tablet. There’s no need to stretch the content to full screen width, because reading text with a width of 2560 px can be uncomfortable. At the same time, you don't want to break the tables.&lt;/p&gt;

&lt;p&gt;Create a fluid grid by setting the maximum and minimum width of the content area. Set the minimum margins from the content part and make a central alignment. The content display will change in proportion to the screen width, but the user will still be able to read text and tables.&lt;/p&gt;

&lt;p&gt;You should also consider the option of hiding the sidebar, so that the user could expand the content area if necessary. This is especially important when working on devices with a screen width of 1024 px.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nRyuzEam--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jbg18qv9qajcvf97zy3q.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nRyuzEam--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jbg18qv9qajcvf97zy3q.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;Principle of grid operation on the new portal&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Most developers use devices with wide screens. Still, it’s better to provide the option of working with the portal on tablets. You can open the sidebar on mobile devices over the content area. It allows users to keep the information in a readable form.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Portal redesign in accordance with the current design system and creating missing components
&lt;/h2&gt;

&lt;p&gt;Portal API entities differ from standard website entities. I highlighted several components that required special attention from the design team.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP methods&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HTTP methods (GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, and PATCH) are one of the most important API entities. Depending on the type of action, the entity has a specific HTTP method. When designing HTTP methods, try to follow these recommendations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assign a color ID to each of the nine existing HTTP methods so that you can visually distinguish them without confusing the user.&lt;/li&gt;
&lt;li&gt;Add a URL to the component of the HTTP method to make life easier for developers. They won’t need to perform additional actions to view this information.&lt;/li&gt;
&lt;li&gt;Make it possible to see both the query and the example at the same time. In the new version of the Wrike API portal, examples are placed inside the method instead of hidden in the slider.&lt;/li&gt;
&lt;li&gt;Don’t place methods that relate to the same entity on separate pages. In the old version, Query Tasks, Create Task, and Modify Task were separate pages. Instead, divide the pages by entities: Contacts, Users, Tasks, and so on. This will also simplify working with the portal.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qqjesTEI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/u8e5y64m4h5t560kejcy.jpeg" alt="Alt Text"&gt;HTTP methods on the new portal
&lt;br&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Tables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All requests sent to the server are presented as tables with a large amount of data.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Pf_VkBVA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/t5rzgz8f3tgm3qta8i12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Pf_VkBVA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/t5rzgz8f3tgm3qta8i12.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;Table on the old version of the portal&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Try to avoid piling up information. In the previous version, the mandatory parameter was indicated by a badge with an inscription. This not only created visual noise but also contradicted the &lt;a href="https://www.w3.org/WAI/WCAG21/quickref/"&gt;site's accessibility requirements&lt;/a&gt;. In the new version, a separate Required column is allocated for this parameter.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EcjAZehE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tlpb1261nm9p500dmhcu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EcjAZehE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/tlpb1261nm9p500dmhcu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;Table on the new portal&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Try to make nesting as obvious as possible and specify which parameter it refers to. In the previous version of the portal, only the description column was highlighted in color, even though the nesting was related to the entire parameter.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uy4JLfJh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bqzzsmj1breun1pj24bm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uy4JLfJh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bqzzsmj1breun1pj24bm.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;Nesting on the previous version of the portal&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;In the new portal, a chevron refers to the parameter itself and helps the user understand that there’s content hidden inside.&lt;/p&gt;

&lt;p&gt;Please note that the columns of a nested table are better to place under the columns of the parent table. It facilitates perception of information.&lt;/p&gt;

&lt;p&gt;Let’s keep the focus on the selected information. The user might have problems focusing when working with a large amount of data. For more visual convenience, hovering the mouse over a line can be highlighted with color. Highlighting a nested table and keeping the ability to select a row inside it is also a good idea.&lt;br&gt;
&lt;br&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wbj-Ozjz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/c09ex3k186ckfdvg1w2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wbj-Ozjz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/c09ex3k186ckfdvg1w2m.png" alt="Alt Text"&gt;&lt;/a&gt;Nesting on the new portal&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An example shows how to work with a specific method and what will be returned if the request is correct. The request is generated using CURL by specifying the necessary data.&lt;/p&gt;

&lt;p&gt;Examples must be visually separated from the rest of the information and set with a fixed height so that the scroll is inside the components, not inside the page. It will save users from endless scrolling.&lt;/p&gt;

&lt;p&gt;For comfortable work with the example, you can add an option of copying to the buffer.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wFVralGL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3azkq50rmj85n4o32mt9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wFVralGL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/3azkq50rmj85n4o32mt9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;Examples on the new portal&lt;br&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Compliance with accessibility and theming
&lt;/h2&gt;

&lt;p&gt;Sites should not only be logical, convenient, and good-looking but also accessible for users with disabilities. As mentioned above, most of the portal components comply with the &lt;a href="http://gov.design/blog/2016/11/08/accessibility.html"&gt;AA accessibility level&lt;/a&gt; and the &lt;a href="https://inclusivedesignprinciples.org/"&gt;principles of inclusive design&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The contrast of plain text, titles, and graphic elements meet the &lt;a href="https://www.w3.org/WAI/standards-guidelines/"&gt;WCAG&lt;/a&gt; standards.&lt;/p&gt;

&lt;p&gt;We provide the ability of tabulation, i.e., the site is manageable and accessible for the keyboard.&lt;/p&gt;

&lt;p&gt;Each graphic object has a description. For example, for buttons that copy information to the buffer, the text “Copy” is located next to the icon. When you click the button, the text changes to “Copied CURL example.”&lt;/p&gt;

&lt;p&gt;If you make an error in the application registration form, in addition to a text message and highlighting the input in red, a graphic symbol appears in the input field so that the user understands which field contains the error and can easily correct the entered value.&lt;/p&gt;

&lt;p&gt;Giving the user &lt;strong&gt;a choice of interface themes&lt;/strong&gt; deserves special mention. It has become a great advantage of the new portal for several reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It's a friendly solution that conforms to one of the main principles of inclusive design: the principle of choice.&lt;/li&gt;
&lt;li&gt;Dark mode is the most popular among developers.&lt;/li&gt;
&lt;li&gt;Dark mode adjusts the screen brightness according to lighting conditions and reduces eye strain.&lt;/li&gt;
&lt;li&gt;Providing themes with improved contrast makes the site accessible to people with impaired vision or color blindness.

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qiTNdanZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cjr53p15iedspmm5vucn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qiTNdanZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/cjr53p15iedspmm5vucn.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;Theming on the Wrike developer portal
&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;To summarize&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To create a user-friendly developer portal interface, you should:&lt;br&gt;
&lt;/p&gt;
&lt;ul&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Make navigation logical, comfortable, and understandable by separating technical content from informational content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Show the user the portal structure with a nesting level no deeper than two at first.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Predict the behavior of the grid at different resolutions for optimal display of content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pay attention to the design of methods by highlighting them in color.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get rid of visual noise of icons in tables — text works better than symbols.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep the focus on the selected information.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Comply with accessibility standards and the principles of inclusive design.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see how we did it all on the &lt;a href="https://developers.wrike.com/"&gt;Wrike developer portal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope you’ve learned something useful about API portals and that this article will help you when designing or redesigning the API portal for your company. We won’t rest on our laurels but will continue to improve our portal.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;//The article is written by Angelina Kulichkova, Web Designer @ Wrike&lt;/em&gt;&lt;/p&gt;


&lt;/ul&gt;

</description>
      <category>design</category>
      <category>api</category>
      <category>interface</category>
      <category>a11y</category>
    </item>
  </channel>
</rss>
