<?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>DEV Community: Alexey Yuzhakov</title>
    <description>The latest articles on DEV Community by Alexey Yuzhakov (@sibprogrammer).</description>
    <link>https://dev.to/sibprogrammer</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%2F1104852%2F65c5deee-6f86-4d3e-8007-d3695b7847c7.jpeg</url>
      <title>DEV Community: Alexey Yuzhakov</title>
      <link>https://dev.to/sibprogrammer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sibprogrammer"/>
    <language>en</language>
    <item>
      <title>Modern Books for Software Engineering Managers</title>
      <dc:creator>Alexey Yuzhakov</dc:creator>
      <pubDate>Wed, 22 Jan 2025 10:26:32 +0000</pubDate>
      <link>https://dev.to/sibprogrammer/modern-books-for-software-engineering-managers-199p</link>
      <guid>https://dev.to/sibprogrammer/modern-books-for-software-engineering-managers-199p</guid>
      <description>&lt;p&gt;The main goal of this article is to share the list of books that are worth reading and practical for the Software Engineering Manager role or similar, where the mix of technical skills and people management comes into play.&lt;/p&gt;

&lt;p&gt;There are many interesting books, but I would like to emphasize those published over the last few years. Our industry evolves very quickly. Classic books may still be good and valuable, but considering time limits, modern technologies, and approaches, it’s better to start with something as practical as possible to get up to speed. &lt;/p&gt;

&lt;p&gt;Last but not least, I saw so many threads on Twitter where various folks promoted “curated lists of books” without reading a page of any of them. Yes, I read all the mentioned books and even &lt;a href="https://www.goodreads.com/review/list/158524207-alexey-yuzhakov?shelf=read&amp;amp;sort=date_read" rel="noopener noreferrer"&gt;more&lt;/a&gt; 🙂 So, I suggest the books I read from cover to cover and tried to apply pieces of advice in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;The list of books published recently that I found worth reading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Become an Effective Software Engineering Manager&lt;/li&gt;
&lt;li&gt;Debugging Teams: Better Productivity through Collaboration&lt;/li&gt;
&lt;li&gt;The Manager's Path: A Guide for Tech Leaders Navigating Growth and Change&lt;/li&gt;
&lt;li&gt;Managing Humans: Biting and Humorous Tales of a Software Engineering Manager&lt;/li&gt;
&lt;li&gt;Leading Effective Engineering Teams&lt;/li&gt;
&lt;li&gt;Radical Candor&lt;/li&gt;
&lt;li&gt;No Rules Rules: Netflix and the Culture of Reinvention&lt;/li&gt;
&lt;li&gt;Engineering Management for the Rest of Us&lt;/li&gt;
&lt;li&gt;An Elegant Puzzle: Systems of Engineering Management&lt;/li&gt;
&lt;li&gt;The Missing README: A Guide for the New Software Engineer&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Become an Effective Software Engineering Manager &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/Become-Effective-Software-Engineering-Manager/dp/1680507249/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh76z8umplyy6dkchs0hj.jpg" alt="Become an Effective Software Engineering Manager" width="250" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My most loved book is “&lt;a href="https://www.amazon.com/Become-Effective-Software-Engineering-Manager/dp/1680507249/" rel="noopener noreferrer"&gt;Become an Effective Software Engineering Manager&lt;/a&gt;.” It competes with “&lt;a href="https://www.amazon.com/Managers-Path-Leaders-Navigating-Growth/dp/1491973897/" rel="noopener noreferrer"&gt;The Manager’s Path&lt;/a&gt;” in the category of “comprehensive guide.” The book is rather big but not bloated, with an excellent focus on bringing practical advice and sharing the author’s hands-on experience on the path of becoming an engineering manager. Best practices are explained as “tools” you must use and “rules” to follow. The book is written in a “first-person shooter” manner. You start at the beginning of this career path and “attack” the problems you observe. It is easy to read, and interesting to see what will happen next. I would not say it is focused on effectiveness, as stated in the title, but using all presented “tools” should level up you as a manager. &lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Teams &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/Debugging-Teams-Productivity-through-Collaboration/dp/1491932058" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd0808cx7q109n1odhjo.jpg" alt="Debugging Teams" width="200" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another excellent book, “&lt;a href="https://www.amazon.com/Debugging-Teams-Productivity-through-Collaboration/dp/1491932058" rel="noopener noreferrer"&gt;Debugging Teams&lt;/a&gt;,” was written by two Google managers. It’s just ~200 pages long and has many satiric illustrations that make reading easy and fun. I found this book very practical and useful. Despite the fact that it was written by people working in a big tech company, it’s suitable for managers of 3-5 member teams as well. This book is worth reading if you are concerned about the team's productivity and how to improve it. The main theme of the book is an interconnection of three traits: humility (don’t put yourself in the first place), respect (to the team, organization, users), and trust (to the same things). The authors did a great job by providing a lot of examples to demonstrate the influence of all these aspects on the productivity of the team.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Manager's Path &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/Managers-Path-Leaders-Navigating-Growth/dp/1491973897/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo4qu804g8iiqq261b7c5.jpg" alt="The Manager's Path" width="200" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This book is one of the most popular recommendations. I suggest choosing “&lt;a href="https://www.amazon.com/Managers-Path-Leaders-Navigating-Growth/dp/1491973897/" rel="noopener noreferrer"&gt;The Manager’s Path&lt;/a&gt;” if you have time to read only one book. The book covers a wide range of topics and sheds light on the manager’s path from tech lead to CTO. You can use it as a handbook and look for the answers to particular questions and use step-by-step instructions. So, the book is very practical. It also has a lot of “Ask CTO” snippets with answers to controversial questions like “I still want to write code” or “hiring interns.” Every chapter ends with “assessing your own experience” exercises. If you provide fair answers, it should definitely help you understand your areas for improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Humans &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/dp/1484271157/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frgk1k3ni9ne43irhz3dj.jpg" alt="Managing Humans" width="198" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you worked as an engineering manager for several years, you would like to read the “&lt;a href="https://www.amazon.com/dp/1484271157/" rel="noopener noreferrer"&gt;Managing Humans&lt;/a&gt;” book. These “biting and humorous” stories are so good. The author worked at famous places like Borland, Netscape, Apple, Palantir, Pinterest, and Slack and faced many different situations. But these are not only “funny stories.” This is knowledge sharing by a very experienced manager. If you have worked as an engineer manager for a while, there is a probability that some of these stories will be very familiar to you. Do some of your engineers hate you? The story “Wallace Hates Me” could help you find the cue and what to do next. Are you struggling to find the time “to think”? There will be insights on how to distinguish the real thinking process from just reacting. Running the meetings is always a challenge. There will be a classification of “meeting creatures” like “Laptop Larry,” “Mr. Irrelevant,” “Chatty Patty,” and many others. There is a high chance you have met them already in your meetings. The book starts with a story titled “Don’t be a Prick” and holds the attention till the last story. So, in general, this book is an interesting reflection on how things work in engineering management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Leading Effective Engineering Teams &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/Leading-Effective-Engineering-Teams-Contributors/dp/109814824X/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5jxkfgzc65vx6ulj2w6.jpg" alt="Leading Effective Engineering Teams" width="200" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“&lt;a href="https://www.amazon.com/Leading-Effective-Engineering-Teams-Contributors/dp/109814824X/" rel="noopener noreferrer"&gt;Leading Effective Engineering Teams&lt;/a&gt;” is one more book from Googler. This time, the word “effective” in the title plays a central role in the book. If you have read about “Project Oxygen” and “Project Aristotle” before, you will find a lot of repetition here. If you haven’t read about them, the book's author provided a better and more detailed explanation of that projects’ key findings than other publicly available articles. Plus, he provides wisdom from his decade-long Google experience on building truly effective teams. The book is full of “bullet-style” instructions and suggestions that could be helpful. However, I am personally not a big fan of that writing style as it quickly becomes hard to read and follow these neverending lists. Moreover, I got the impression that many things in the book, like “ask good questions” and “use the right tools,” look so idealistic. The equations like “hire super talented people, build a highly effective team, and create a valuable product” are easier to proclaim than to solve in real life. But it’s still worth reading. At least if you want to know what an ideal engineering manager’s life should look like. &lt;/p&gt;

&lt;h2&gt;
  
  
  Radical Candor &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/Radical-Candor-Revised-Kick-Ass-Humanity/dp/1250235375/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjxsc0eil62jpwhzcf736.jpg" alt="Radical Candor" width="197" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I tried starting to read &lt;a href="https://www.amazon.com/Radical-Candor-Revised-Kick-Ass-Humanity/dp/1250235375/" rel="noopener noreferrer"&gt;this book&lt;/a&gt; three times and finished it only on the third attempt. Eventually, I realized that if I pushed through the first 30-40 pages, I could get used to the writing style and keep reading. So, if you are struggling like me, try to use this piece of advice.&lt;br&gt;
It’s very hard to build an effective team of talented engineers. But it’s even harder to retain them. The author of the book introduces the “Radical Candor” philosophy and describes its practical application in Apple and Google. How to be a great boss without losing your humanity? This is a tough question, and you can find not only a theoretical background but practical advice on how to answer it. I wouldn’t say this is a book for recently promoted managers, but for people who have worked as managers for a while and would like to level up their management skills. &lt;/p&gt;

&lt;h2&gt;
  
  
  No Rules Rules &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/No-Rules-Netflix-Culture-Reinvention/dp/1984877860/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdoait4rm8l9lubw3cfc.jpg" alt="No Rules Rules" width="195" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although the word “radical” was in the title of the previous book, “&lt;a href="https://www.amazon.com/No-Rules-Netflix-Culture-Reinvention/dp/1984877860/" rel="noopener noreferrer"&gt;No Rules Rules&lt;/a&gt;” is the most radical book I have read about engineering management. The book describes how processes are organized at Netflix. Most probably, you will be unable to apply all these practices in your company. Maybe even none of them. But you will definitely be impressed by how some ordinary work can be organized in very different ways. For example, if you want to spend $60 on some cloud service and you have to prepare a written justification, ask three different people for approval, and wait for two weeks, it’s not a surprise why your work goes very slowly, and there is no room for innovations. On the other hand, giving people the freedom to spend the company’s money as they wish could sound uncomfortable, at least. &lt;br&gt;
One of the often cited parts of this book is a “Keeper Test.” If some of your engineers wanted to leave, would you fight for them? If the answer is no… Well, just read the book to get the answer. Even if you are not ready for radical steps, doing the “Keeper Test” is an excellent exercise to be prepared for tough times. &lt;br&gt;
No Rules Rules are about rules. But absolutely different rules. After reading this book, you may realize these rules are so insane to be applied in the real world. However, Netflix is a successful company and an absolutely real one. &lt;br&gt;
This book should be interesting, especially for top managers and company owners who struggle with a lack of innovations and a slow pace of work. &lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering Management for the Rest of Us &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/Engineering-Management-Rest-Sarah-Drasner/dp/B0BHX6NLGZ/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2bftc2xvon7tnt0p99l.jpg" alt="Engineering Management for the Rest of Us" width="209" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;“&lt;a href="https://www.amazon.com/Engineering-Management-Rest-Sarah-Drasner/dp/B0BHX6NLGZ/" rel="noopener noreferrer"&gt;Engineering Management for the Rest of Us&lt;/a&gt;” is an interesting book, focusing mainly on the fact that most engineering managers had no prior education in management and struggled with crises and many unfamiliar problems after becoming managers. &lt;br&gt;
I had the strange impression that the chapters were a compilation of presentations. I know she is a presenter, but I have never seen her presentations. The book is quite short, with many topics covered very briefly. Despite that fact, the author tries to provide a lot of useful examples. If you are looking for a short introduction to the topic of engineering management, this is the right book for you. &lt;/p&gt;

&lt;h2&gt;
  
  
  An Elegant Puzzle &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/Elegant-Puzzle-Systems-Engineering-Management/dp/1732265186/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzdgv2lakpjzduid1h64h.jpg" alt="An Elegant Puzzle" width="203" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First of all, “&lt;a href="https://www.amazon.com/Elegant-Puzzle-Systems-Engineering-Management/dp/1732265186/" rel="noopener noreferrer"&gt;An Elegant Puzzle&lt;/a&gt;” book should be interesting for a manager of managers. The author worked at several famous companies, like Uber and Stripe, and shared his experience in this book. If you struggle with questions about the size of the engineering team or how to select project leads, you will find practical suggestions with detailed explanations. I found the chapters are quite independent of each other, and there is no strong storyline. Most probably because, initially, these articles were blog posts. However, there is a benefit to such an approach as well. You can use this book as a handbook and read only about the topic you are interested in to get practical advice and step-by-step instructions. Software engineering, in general, and engineering management, in particular, is often puzzling. Books like this help a lot in becoming good at solving such puzzles.  &lt;/p&gt;

&lt;h2&gt;
  
  
  The Missing README &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/Missing-README-Guide-Software-Engineer/dp/1718501838/" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn2scafehd06m0flwl31y.jpg" alt="The Missing README" width="199" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first glance, &lt;a href="https://www.amazon.com/Missing-README-Guide-Software-Engineer/dp/1718501838/" rel="noopener noreferrer"&gt;this book&lt;/a&gt; does not look like it is for managers but for new software engineers. Why do I suggest it? The reason is simple: sooner or later, every manager faces onboarding a new member to the project. Every project has its specifics. But there are a lot of common things as well. Instead of reinventing the wheel by writing your own instructions for newcomers, you can utilize the wisdom from this book. With great clarity, the authors describe almost every aspect software engineers will meet during their work and provide ready-to-use instructions. During my work, I found a lot of practical examples from this book that helped me save time during discussions with engineers. &lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Do not fool yourself: reading about something is not the same as doing that. But knowledge can be very powerful. If you are facing a challenging situation and are already armed with knowledge, you’re increasing your odds of finding the right solution and making a more informed decision. &lt;/p&gt;

&lt;p&gt;I’ve been working as an engineering manager for a long time, and I wish I read all these books before I started. It would have saved me a lot of time and nerves. Learning only from your own mistakes is very expensive and sensitive in management. You can fix a software bug you made earlier, but it’s much more complicated to reverse the decision you made to hire or fire someone. &lt;/p&gt;

&lt;p&gt;I would be very glad if you found some of these books interesting and read them. If you would like to share your favorite book about software engineering management, feel free to do so in the comments.&lt;/p&gt;

</description>
      <category>books</category>
      <category>management</category>
      <category>career</category>
      <category>programming</category>
    </item>
    <item>
      <title>JIRA Analytics with Pandas</title>
      <dc:creator>Alexey Yuzhakov</dc:creator>
      <pubDate>Fri, 23 Aug 2024 11:04:39 +0000</pubDate>
      <link>https://dev.to/sibprogrammer/jira-analytics-with-pandas-agl</link>
      <guid>https://dev.to/sibprogrammer/jira-analytics-with-pandas-agl</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;It's hard to argue &lt;a href="https://www.atlassian.com/software/jira" rel="noopener noreferrer"&gt;Atlassian JIRA&lt;/a&gt; is one of the most popular issue trackers and project management solutions. You can love it, you can hate it, but if you were hired as a software engineer for some company, there is a high probability of meeting JIRA. &lt;/p&gt;

&lt;p&gt;If the project you are working on is very active, there can be thousands of JIRA issues of various types. If you are leading a team of engineers, you can be interested in analytical tools that can help you understand what is going on in the project based on data stored in JIRA. JIRA has some reporting facilities integrated, as well as 3rd party plugins. But most of them are pretty basic. For example, it's hard to find rather flexible "forecasting" tools.&lt;/p&gt;

&lt;p&gt;The bigger the project, the less satisfied you are with integrated reporting tools. At some point, you will end up using an API to extract, manipulate, and visualize the data. During the last 15 years of JIRA usage, I saw dozens of such scripts and services in various programming languages around this domain.&lt;/p&gt;

&lt;p&gt;Many day-to-day tasks may require one-time data analysis, so writing services every time doesn't pay off. You can treat JIRA as a data source and use a typical data analytics tool belt. For example, you may take &lt;a href="https://jupyter.org/" rel="noopener noreferrer"&gt;Jupyter&lt;/a&gt;, fetch the list of recent bugs in the project, prepare a list of "features" (attributes valuable for analysis), utilize &lt;a href="https://pandas.pydata.org/" rel="noopener noreferrer"&gt;pandas&lt;/a&gt; to calculate the statistics, and try to forecast trends using &lt;a href="https://scikit-learn.org/stable/" rel="noopener noreferrer"&gt;scikit-learn&lt;/a&gt;. In this article, I would like to explain how to do it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Preparation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  JIRA API Access
&lt;/h3&gt;

&lt;p&gt;Here, we will talk about the cloud version of JIRA. But if you are using a self-hosted version, the main concepts are almost the same.&lt;/p&gt;

&lt;p&gt;First of all, we need to create a secret key to access JIRA via REST API. To do so, go to profile management - &lt;a href="https://id.atlassian.com/manage-profile/profile-and-visibility" rel="noopener noreferrer"&gt;https://id.atlassian.com/manage-profile/profile-and-visibility&lt;/a&gt; If you select the "Security" tab, you will find the "Create and manage API tokens" link:&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%2Fuploads%2Farticles%2Fppw2xc0c51l5tt65w15a.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%2Fuploads%2Farticles%2Fppw2xc0c51l5tt65w15a.png" alt="API Tokens Management Link"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new API token here and store it securely. We will use this token later.&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%2Fuploads%2Farticles%2F2o25d9a4vqa631if4c86.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%2Fuploads%2Farticles%2F2o25d9a4vqa631if4c86.png" alt="API Tokens Management"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Jupyter Notebooks
&lt;/h3&gt;

&lt;p&gt;One of the most convenient ways to play with datasets is to utilize &lt;a href="https://jupyter.org/" rel="noopener noreferrer"&gt;Jupyter&lt;/a&gt;. If you are not familiar with this tool, do not worry. I will show how to use it to solve our problem. For local experiments, I like to use &lt;a href="https://www.jetbrains.com/dataspell/" rel="noopener noreferrer"&gt;DataSpell by JetBrains&lt;/a&gt;, but there are services available online and for free. One of the most well-known services among data scientists is &lt;a href="https://www.kaggle.com/code/" rel="noopener noreferrer"&gt;Kaggle&lt;/a&gt;. However, their notebooks don't allow you to make external connections to access JIRA via API. Another very popular service is &lt;a href="https://colab.research.google.com/" rel="noopener noreferrer"&gt;Colab&lt;/a&gt; by Google. It allows you to make remote connections and install additional Python modules.&lt;/p&gt;

&lt;p&gt;JIRA has a pretty easy-to-use &lt;a href="https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#about" rel="noopener noreferrer"&gt;REST API&lt;/a&gt;. You can make API calls using your favorite way of doing HTTP requests and parse the response manually. However, we will utilize an excellent and very popular &lt;a href="https://jira.readthedocs.io/" rel="noopener noreferrer"&gt;jira&lt;/a&gt; module for that purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools in Action
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Data Analysis
&lt;/h3&gt;

&lt;p&gt;Let's combine all the parts to come up with the solution.&lt;/p&gt;

&lt;p&gt;Go to the &lt;a href="https://colab.research.google.com/" rel="noopener noreferrer"&gt;Google Colab&lt;/a&gt; interface and create a new notebook. After the notebook creation, we need to store previously obtained JIRA credentials as "secrets." Click the "Key" icon in the left toolbar to open the appropriate dialog and add two "secrets" with the following names: JIRA_USER and JIRA_PASSWORD. At the bottom of the screen, you can see the way how to access these "secrets":&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%2Fuploads%2Farticles%2Fpftxq364vimsomtmf5nc.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%2Fuploads%2Farticles%2Fpftxq364vimsomtmf5nc.png" alt="Notebook Secrets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next thing is to install an additional Python module for JIRA integration. We can do it by executing the shell command in the scope of the notebook cell:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

!pip install jira


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The output should look something like the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Collecting jira
  Downloading jira-3.8.0-py3-none-any.whl (77 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 77.5/77.5 kB 1.3 MB/s eta 0:00:00
Requirement already satisfied: defusedxml in /usr/local/lib/python3.10/dist-packages (from jira) (0.7.1)
...
Installing collected packages: requests-toolbelt, jira
Successfully installed jira-3.8.0 requests-toolbelt-1.0.0


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We need to fetch the "secrets"/credentials:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.colab&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;

&lt;span class="n"&gt;JIRA_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://******.atlassian.net&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;JIRA_USER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;JIRA_USER&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;JIRA_PASSWORD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;JIRA_PASSWORD&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And validate the connection to the JIRA Cloud:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jira&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;JIRA&lt;/span&gt;

&lt;span class="n"&gt;jira&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JIRA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JIRA_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basic_auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JIRA_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JIRA_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jira&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;projects&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If the connection is ok and the credentials are valid, you should see a non-empty list of your projects:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[&amp;lt;JIRA Project: key='PROJ1', name='Name here..', id='10234'&amp;gt;,
 &amp;lt;JIRA Project: key='PROJ2', name='Friendly name..', id='10020'&amp;gt;,
 &amp;lt;JIRA Project: key='PROJ3', name='One more project', id='10045'&amp;gt;,
...


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So we can connect and fetch data from JIRA. The next step is to fetch some data for analysis with pandas. Let’s try to fetch the list of solved problems during the last several weeks for some project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;JIRA_FILTER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;19762&lt;/span&gt;

&lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jira&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_issues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;filter=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;JIRA_FILTER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;maxResults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;summary,issuetype,assignee,reporter,aggregatetimespent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We need to transform the dataset into the pandas data frame:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assignee&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assignee&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assignee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;displayName&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregatetimespent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The output may look like the following:&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%2Fuploads%2Farticles%2Fnp9cl6iqmlg04kyjv7yv.jpg" 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%2Fuploads%2Farticles%2Fnp9cl6iqmlg04kyjv7yv.jpg" alt="Issues list as pandas dataframe"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We would like to analyze how much time it usually takes to solve the issue. People are not ideal, so sometimes they forget to log the work. It brings a headache if you try to analyze such data using JIRA built-in tools. But it's not a problem for us to make some adjustments using pandas. For example, we can transform the "time" field from seconds into hours and replace the absent values with the median value (beware, &lt;code&gt;dropna&lt;/code&gt; can be more suitable if there are a lot of gaps):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can easily visualize the distribution to find out anomalies:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xlabel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xticks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fuploads%2Farticles%2Ffzcfhvki2ewv93towd0h.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%2Fuploads%2Farticles%2Ffzcfhvki2ewv93towd0h.png" alt="Time column visualization"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is also interesting to see the distribution of solved problems by the assignee:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;top_solvers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assignee&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;top_solvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tickets&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;top_solvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tickets&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;top_solvers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;barh&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;invert_yaxis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It may look like the following:&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%2Fuploads%2Farticles%2F5t98n53vhrnhd5pv4f17.jpg" 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%2Fuploads%2Farticles%2F5t98n53vhrnhd5pv4f17.jpg" alt="Top solvers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Predictions
&lt;/h3&gt;

&lt;p&gt;Let's try to predict the amount of time required to finish all open issues. Of course, we can do it without machine learning by using simple approximation and the average time to resolve the issue. So the predicted amount of required time is the number of open issues multiplied by the average time to resolve one. For example, the median time to solve one issue is 2 hours, and we have 9 open issues, so the time required to solve them all is 18 hours (approximation). It's a good enough forecast, but we might know the speed of solving depends on the product, team, and other attributes of the issue. If we want to improve the prediction, we can utilize machine learning to solve this task.&lt;/p&gt;

&lt;p&gt;The high-level approach looks the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Obtain the dataset for “learning”&lt;/li&gt;
&lt;li&gt;Clean up the data&lt;/li&gt;
&lt;li&gt;Prepare the "features" aka "feature engineering"&lt;/li&gt;
&lt;li&gt;Train the model&lt;/li&gt;
&lt;li&gt;Use the model to predict some value of the target dataset&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the first step, we will use a dataset of tickets for the last 30 weeks. Some parts here are simplified for illustrative purposes. In real life, the amount of data for learning should be big enough to make a useful model (e.g., in our case, we need thousands of issues to be analyzed). &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jira&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_issues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;project = PPS AND status IN (Resolved) AND created &amp;gt;= -30w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;maxResults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;summary,issuetype,customfield_10718,customfield_10674,aggregatetimespent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;closed_tickets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customfield_10718&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customfield_10674&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregatetimespent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;closed_tickets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;closed_tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closed_tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;closed_tickets&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In my case, it's something around 800 tickets and only two fields for "learning": "team" and "product."&lt;/p&gt;

&lt;p&gt;The next step is to obtain our target dataset. Why do I do it so early? I want to clean up and do "feature engineering" in one shot for both datasets. Otherwise, the mismatch between the structures can cause problems.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jira&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search_issues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;project = PPS AND status IN (Open, Reopened)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;maxResults&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;summary,issuetype,customfield_10718,customfield_10674&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;open_tickets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customfield_10718&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customfield_10674&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;open_tickets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;open_tickets&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Please notice we have no "time" column here because we want to predict it. Let's nullify it and combine both datasets to prepare the "features."&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;open_tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;tickets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;closed_tickets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;open_tickets&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;tickets&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Columns "team" and "product" contain string values. One of the ways of dealing with that is to transform each value into separate fields with boolean flags. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_dummies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tickets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;teams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_dummies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tickets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;teams&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;team&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tickets&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The result may look like the following:&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%2Fuploads%2Farticles%2Fecqpgcuvslq4vtporxsd.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%2Fuploads%2Farticles%2Fecqpgcuvslq4vtporxsd.png" alt="Dataset preparation and cleanup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the combined dataset preparation, we can split it back into two parts:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;closed_tickets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closed_tickets&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;open_tickets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closed_tickets&lt;/span&gt;&lt;span class="p"&gt;):][:]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now it's time to train our model:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.model_selection&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;train_test_split&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.tree&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DecisionTreeRegressor&lt;/span&gt;

&lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;closed_tickets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;closed_tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;features_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;features_val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;train_test_split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DecisionTreeRegressor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features_train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels_train&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features_val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;labels_val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And the final step is to use our model to make a prediction:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;open_tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;open_tickets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;open_tickets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The final output, in my case, is 25 hours, which is higher than our initial rough estimation. This was a basic example. However, by using ML tools, you can significantly expand your abilities to analyze JIRA data. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Sometimes, JIRA built-in tools and plugins are not sufficient for effective analysis. Moreover, many 3rd party plugins are rather expensive, costing thousands of dollars per year, and you will still struggle to make them work the way you want. However, you can easily utilize well-known data analysis tools by fetching necessary information via JIRA API and go beyond these limitations. I spent so many hours playing with various JIRA plugins in attempts to create good reports for projects, but they often missed some important parts. Building a tool or a full-featured service on top of JIRA API also often looks like overkill. That's why typical data analysis and ML tools like Jupiter, pandas, matplotlib, scikit-learn, and others may work better here. &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%2Fuploads%2Farticles%2F4beq80368iwl0o44jhqd.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%2Fuploads%2Farticles%2F4beq80368iwl0o44jhqd.png" alt="JIRA analytics made easy"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>management</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Build Binary Tree from Array</title>
      <dc:creator>Alexey Yuzhakov</dc:creator>
      <pubDate>Wed, 31 Jan 2024 17:06:57 +0000</pubDate>
      <link>https://dev.to/sibprogrammer/build-binary-tree-from-array-1f5o</link>
      <guid>https://dev.to/sibprogrammer/build-binary-tree-from-array-1f5o</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;If you are interested in algorithms, data structures, and building efficient solutions or just preparing for the coding interview, you are aware of &lt;a href="https://leetcode.com/" rel="noopener noreferrer"&gt;LeetCode&lt;/a&gt; and similar websites. Here, I will talk about a data structure called &lt;a href="https://en.wikipedia.org/wiki/Binary_tree" rel="noopener noreferrer"&gt;Binary Tree&lt;/a&gt; and the ways to build it using the array representation. LeetCode has &lt;a href="https://leetcode.com/tag/binary-tree/" rel="noopener noreferrer"&gt;dozens of such problems&lt;/a&gt; to practice with this data structure. &lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;One of the ways to store the tree is to use an array representation. It’s hard for humans to analyze it, but it can be compact and convenient for machine processing. LeetCode is not an exception, so the typical problem describes the input as an array, converts it to the tree representation, and expects you to provide a solution. But if you want to experiment with the algorithm in your favorite IDE, like PyCharm, instead of a built-in LeetCode editor, here is a problem. The conversion of the array to the tree representation is hidden, and you need to implement it on your own.&lt;/p&gt;

&lt;p&gt;The tree node is represented as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;An example of the input is below:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[1, 2, 3, null, 4, null, null, 5, 6, null, 7]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Visual representation of such a tree looks like the following:&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%2Fuploads%2Farticles%2F5jwtp1gc3qtmm32lsswv.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%2Fuploads%2Farticles%2F5jwtp1gc3qtmm32lsswv.png" alt="Binary tree"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to create a function that accepts an array as a parameter and returns the root node of the created tree:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TreeNode&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO: implement
&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Approach #1
&lt;/h2&gt;

&lt;p&gt;At first glance, it looks like the classic approach with 2i + 1 for the left child index and 2i + 2 for the right one should work well. Here is a visual representation of how to find the indexes for children nodes during the array traversal.&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%2Fuploads%2Farticles%2Fqo1nxmmoytxkn01cvtph.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%2Fuploads%2Farticles%2Fqo1nxmmoytxkn01cvtph.png" alt="Array traversal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The recursive implementation may look like the following:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TreeNode&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;An example of the call:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I use &lt;code&gt;None&lt;/code&gt; to make Python syntax valid and represent absent nodes. We need to notice our tree is not a “complete binary tree” or “binary heap” where all levels, except possibly the last, are completely filled, and nodes on the last level go from left to right. So it’s ok to have the missed nodes. And here is a gotcha! LeetCode uses the specific array representation. Take a look at the visualization once again:&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%2Fuploads%2Farticles%2F5jwtp1gc3qtmm32lsswv.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%2Fuploads%2Farticles%2F5jwtp1gc3qtmm32lsswv.png" alt="Binary tree"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The node with value 4 has two children: 5 and 6. Let’s enumerate our array with indexes:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[0: 1, 1: 2, 2: 3, 3: None, 4: 4, 5: None, 6: None, 7: 5, 8: 6, 9: None, 10: 7]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The node with value 4 has an index 4. Our formula tells us that the left child of it is calculated as follows: 2*4+1=9 and the right one: 2*4=10. But it’s &lt;code&gt;None&lt;/code&gt; and 7 values instead of 4 and 5. The correct array representation to comply with the formula should look like the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

[1, 2, 3, None, 4, None, None, None, None, 5, 6, None, None, None, None, None, None, None, None, None, 7, None, ...]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, there are a lot of None values, and the total number of elements is much higher. We are talking about the binary tree, and the tree in the example has 5 levels. So, the number of items to store all the possible values is 1+2+2*2+2*2*2+... = 31. Meanwhile, our tree has 7 values and uses only 11 array items to store them.&lt;/p&gt;

&lt;p&gt;To validate the correctness of the tree, we can implement a simple string representation of the class (nesting represents the parent-child relationship):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__repr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;traverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;
            &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;traverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;traverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;traverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let’s print out our tree:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    (1)
      (2)
        (4)
          (7)
      (3)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The created tree is incorrect. LeetCode uses a more compact representation, which detects children's node offsets using the other way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach #2
&lt;/h2&gt;

&lt;p&gt;To deal with missed values and compact representation, we can utilize the iterative approach and queues this time:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TreeNode&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;left_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;left_val&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left_val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;right_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;right_val&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right_val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I use the list type for illustrative purposes, which can be replaced with queue implementation because we have a lot of &lt;code&gt;pop(0)&lt;/code&gt; operations. &lt;/p&gt;

&lt;p&gt;Let’s validate the correctness of the new method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    (1)
      (2)
        (4)
          (5)
            (7)
          (6)
      (3)


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Everything is correct. The proposed solution works well on the LeetCode’s way of representing the trees as arrays. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The array representation usually stores "binary heaps" or "complete binary trees." But as you can see, some tweaks help to store any binary tree as an array in a rather compact form. But the re-building of the tree from such an array also becomes trickier, and you need to be careful with indexes for missed values.&lt;/p&gt;

&lt;p&gt;The complete source code can be found &lt;a href="https://github.com/sibprogrammer/lc-binary-tree" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>leetcode</category>
      <category>algorithms</category>
      <category>programming</category>
    </item>
    <item>
      <title>SSH as VPN Alternative</title>
      <dc:creator>Alexey Yuzhakov</dc:creator>
      <pubDate>Sat, 04 Nov 2023 13:57:36 +0000</pubDate>
      <link>https://dev.to/sibprogrammer/ssh-as-vpn-alternative-484h</link>
      <guid>https://dev.to/sibprogrammer/ssh-as-vpn-alternative-484h</guid>
      <description>&lt;h2&gt;
  
  
  Internet Openness
&lt;/h2&gt;

&lt;p&gt;During the last decades, the Internet openness principle has become something often ignored and violated. Suppose you travel a lot and want to access the resources located in one region while you are physically in another one. In that case, it is not a surprise anymore to find the resource is inaccessible. The reasons can be different. But one of the popular is that "we suffered from attacks from region X, so we decided to block the access for all the people/IPs from the region X," or even worse, "we decided to allow access only for people of our region based on IP."&lt;/p&gt;

&lt;h2&gt;
  
  
  VPN
&lt;/h2&gt;

&lt;p&gt;I think VPN services became quite popular not only due to security reasons but also as a way to solve the described problem: provide access to a resource regardless of client IP-based limitations. There are a lot of VPN service providers across the globe. Surprisingly, the usage of VPN services &lt;a href="https://www.kaspersky.com/resource-center/definitions/how-does-vpn-keep-me-safe-online" rel="noopener noreferrer"&gt;can be less secure&lt;/a&gt; than it seems at first glance. Okay, you can buy a droplet in DigitalOcean and probably install OpenVPN or WireGuard. But at least it takes time for the initial configuration. If the need for such access is quite infrequent, all these efforts are not worth the time investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSH Tunnel
&lt;/h2&gt;

&lt;p&gt;There is some chance that you, like me, already have a virtual or physical server with SSH in the region to which you want access. For example, sitting in Sofia, Bulgaria, I want to check some websites hosted in Germany. Meanwhile, I have a DigitalOcean droplet located in Frankfurt, Germany, with SSH access. The SSH client is already in place on my machine. So, the only thing I need to do is establish the SSH tunnel and use a properly configured web browser for accessing these German websites. &lt;/p&gt;

&lt;p&gt;The following command helps to establish the tunnel on 12345 port:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

ssh -D 12345 my-droplet-in-frankfurt.com


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The only difference between typical SSH command is the “-D” flag that instructs the SSH client to listen to the local 12345 port and forwards the traffic from our local machine to the remote server. So, we will access the desired websites "on behalf" of the remote machine.&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%2Fuploads%2Farticles%2Fumefu8deqcf96anvu3ff.jpg" 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%2Fuploads%2Farticles%2Fumefu8deqcf96anvu3ff.jpg" alt="SSH Tunnel Scheme"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My primary browser is Google Chrome. For alternative web browsing through SSH tunnel, I'm using Mozilla Firefox. To setup a proxy, one should go to Settings -&amp;gt; Network Settings and fill in the appropriate fields highlighted in the screenshot below:&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%2Fuploads%2Farticles%2Fkf51zian9lggc5psk5vf.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%2Fuploads%2Farticles%2Fkf51zian9lggc5psk5vf.png" alt="Firefox proxy settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;SSH tunnel looks like a typical SSH session. So you can quit it as soon as you finish your web browsing of restricted websites. You also don't need to change your Firefox configuration every time you need to access different websites. Just establish the SSH tunnel to the new location, open Firefox, and start browsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;SSH tunnel is an often overlooked alternative to the full-featured VPN services. But for a single person, occasional usage, the SSH tunnel can be a simpler and more convenient way of accessing restricted websites. &lt;/p&gt;

</description>
      <category>security</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Build an Open Source Project: Behind the Scenes</title>
      <dc:creator>Alexey Yuzhakov</dc:creator>
      <pubDate>Sun, 02 Jul 2023 18:11:04 +0000</pubDate>
      <link>https://dev.to/sibprogrammer/build-an-open-source-project-behind-the-scenes-54ii</link>
      <guid>https://dev.to/sibprogrammer/build-an-open-source-project-behind-the-scenes-54ii</guid>
      <description>&lt;h2&gt;
  
  
  Starting Point
&lt;/h2&gt;

&lt;p&gt;One of my favorite hobbies is working on Open Source projects. Usually, it starts with solving my own problem. At some point in time, I can assume other people may find my experiments useful, and it's time to open-source the project. I see a lot of people think it’s enough to push the code to GitHub to open-source their project. Technically, yes, it's an essential step. Is it enough? Definitely, not. &lt;/p&gt;

&lt;p&gt;If you decide to open-source your project, the first preparation steps can be found at &lt;a href="https://opensource.guide/starting-a-project/"&gt;https://opensource.guide/starting-a-project/&lt;/a&gt; guideline. If you didn’t read it, I highly recommend doing that. It’s still not enough, but a good starting point.&lt;/p&gt;

&lt;p&gt;Some time ago, I started a project called "&lt;a href="https://github.com/sibprogrammer/xq"&gt;xq&lt;/a&gt;", which is a command-line XML and HTML beautifier and content extractor written in Go. Using this project as an example, I want to show what I did to make it a little bit more discoverable and usable by other people. &lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Well, you already pushed the source code of your project to GitHub. Why do you need to read next? Very soon, you may realize nobody is using your cool stuff that you were building by night last year. It can be disappointing.&lt;/p&gt;

&lt;p&gt;The community around my projects motivates me to continue working on them and improving them, bringing some interesting ideas and even code contributions. It doesn’t matter if it's an Open Source project or a commercial one. It is always inspiring to see if your tool is used by hundreds, thousands, or millions of people. The more, the better. Meanwhile, as far as I always start such projects to solve my own problems, I benefit a lot from the growing amount of feedback.  &lt;/p&gt;

&lt;p&gt;Another thing that motivates me a lot is the ability to experiment with technologies and try new things. I have a regular job and work on the commercial product. It's always hard to bring something new and unreliable because commercial products are not playgrounds. So my open source initiatives always helped me to get some knowledge of technologies I can't use or experiment with on the main job.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Target Audience
&lt;/h2&gt;

&lt;p&gt;It's quite important to analyze the potential target audience of your project in detail for the project's success. I often see how creators and maintainers expect the same level of skills from their project users. In most cases, it’s a misunderstanding that becomes a problem for both parties. &lt;/p&gt;

&lt;p&gt;For example, for my "xq" utility which deals with XML in CLI and is written in Go, I expect the users to know what XML is for and how to use command-line utilities. But I don’t expect the knowledge of Go, the corresponding toolchain, or even any coding skills at all. &lt;/p&gt;

&lt;h2&gt;
  
  
  Usability
&lt;/h2&gt;

&lt;p&gt;The next important thing is to think about your project as a product that is at least easy to install and start using. Ideally, the value of the product is absolutely clear for the end-user from the first seconds of your product usage.&lt;/p&gt;

&lt;p&gt;We like to push our Open Source project to GitHub, but it's code-centric. We should help our users with clear and easy installation instructions. And here is a trap. I use Mac and want my "xq" utility installable using &lt;a href="https://brew.sh/"&gt;Homebrew&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install xq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I checked the guidelines on how to contribute to Homebrew and found that if your repo has zero "stars", you have zero chances to be added. One needs to find alternatives to start with. Ok, Go provides a facility for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go install github.com/sibprogrammer/xq@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, it looks good, but it limits the audience to people who already have the Go toolchain installed. It's quite a strict limitation. So in my case, a good starting point was the bash installer for the CLI utility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sSL https://bit.ly/install-xq | sudo bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After gaining the first 50-70 "stars", we will be ready for new endeavors like &lt;code&gt;brew&lt;/code&gt; or &lt;code&gt;apt install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But even before thinking about how to make the installation process easy, we need to attract a potential user and show the value of our product. I really like the projects which have a screenshot or animated video that demonstrates the main features right at the beginning of the README.md file. If we are talking about CLI utilities, the comprehensive set of usage examples is also an essential part. If it’s some kind of web product, a link to preinstalled demo server is a must. Life is short, and we all are very busy. You probably have no sales team to convenience the potential users, and people may evaluate your product not longer than just 1-2 minutes. &lt;/p&gt;

&lt;h2&gt;
  
  
  Marketing
&lt;/h2&gt;

&lt;p&gt;Well, for the majority of us, the word "marketing" has no association with something good, interesting, and what we want to do. But it's not enough to have a good product, we need to tell other people about it somehow.&lt;/p&gt;

&lt;p&gt;It is not so hard to start with friends and local communities. Twitter, Reddit, Facebook, LinkedIn, and other social networks may help you to gain the first feedback and attract the first users. I want to tell you the story about "xq" marketing efforts to show how it works in real life. &lt;/p&gt;

&lt;p&gt;The company I work for has an initiative called "Research Days," and there is a special event named "Research Days Demos." So, the first presentation I did was at this event for my colleagues. There were not so many people, but some of them liked the utility. I also made a short post in the internal Slack channel related to similar initiatives.&lt;/p&gt;

&lt;p&gt;The next two attempts were to tell about the utility on Reddit. One was successful (in terms that there was a discussion and the project attracted a few more users), and another one was blocked by the moderator (and I still have no clue why). Eventually, I got enough "stars" to be able to prepare a pull request to join Homebrew.&lt;/p&gt;

&lt;p&gt;Later there was a period of slow organic growth, fixing of bugs, and implementing new features. I researched the ways how to make the installation on Linux more simple, and the assistance of distro maintainers helped a lot with &lt;a href="https://repology.org/project/xq-sibprogrammer/versions"&gt;adoption&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At some point in time, I decided to try the power of &lt;a href="https://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt;, and it was so impressive. Tons of feedback and feature requests alongside the increasing number of "stars." I'm not a very active Twitter user and have a few followers, but after the post to Hacker News, I found there are a lot of bots on Twitter trying to repost every piece of news. There were even a couple of discussions after such reposts. &lt;/p&gt;

&lt;p&gt;There are some more ideas, like joining "&lt;a href="https://github.com/topics/awesome"&gt;awesome&lt;/a&gt;" lists or publishing a dedicated article with a detailed feature set description and use cases. In general, I think it's a good idea, after several months of development, to spend some efforts on marketing to improve the product's discoverability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyomcwugq9g1tjuv6aie6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyomcwugq9g1tjuv6aie6.png" alt="xq stars history" width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Save Your Time
&lt;/h2&gt;

&lt;p&gt;Working on a project can easily become boring due to different chores. Sorting out badly written bug reports, manual testing of every change and etc. may become a main reason for dissatisfaction. Because you are working on an Open Source project, spend your time and do it for free. One of the ways to keep yourself motivated is to be focused on answering the question of how to avoid chores and save time.&lt;/p&gt;

&lt;p&gt;GitHub allows providing custom templates for new issue creation. Do not underestimate. It significantly helps to streamline the reports and forces the people to answer the questions necessary to the maintainer.&lt;/p&gt;

&lt;p&gt;The last thing I want to deal with in the scope of my Open Source projects is the regression bugs. That's why a comprehensive set of tests is a significant time saver and brings the joy of working on the project improvements. Without tests, the maintenance of more or less complex projects after 5-6 release cycles will easily become a nightmare. It's interesting to see how often this truth is ignored.&lt;/p&gt;

&lt;p&gt;Writing a set of tests is not enough. GitHub Actions is an excellent facility to organize the CI process not only for pushes to the master branch but for the pull requests as well. Otherwise, it is quite disappointing to find out after the merge of someone's pull request that code style or even tests are broken. It's not so hard to setup the actions. If you check your favorite Open Source project, I guess all of them will have established CI.&lt;/p&gt;

&lt;p&gt;With "xq", I went even further and automated the release process using &lt;a href="https://goreleaser.com/"&gt;GoReleaser&lt;/a&gt;. To publish a new release, the only thing I need is to create and push the Git tag. The corresponding GitHub Action will trigger a release process, and GoReleaser prepares the binaries and changelog based on declared conventions. The result has a high level of predictability, and no manual work is required.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmezia39ix103qig6zy5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmezia39ix103qig6zy5t.png" alt="GoReleaser" width="600" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Perks
&lt;/h2&gt;

&lt;p&gt;Open Source projects are usually not about making money. At least in a direct way. If you want to create a product and are not sure how to monetize it, starting this product by open-sourcing it can be not a good idea at all from my point of view. &lt;/p&gt;

&lt;p&gt;But there are a lot of other exciting benefits. One of them I already mentioned briefly in the "Motivation" section: learning by doing and working with cutting-edge technologies can be easier in the scope of Open Source projects. Many times after such experiments, I re-used the knowledge in the commercial products as well. &lt;/p&gt;

&lt;p&gt;If we a talking about a career, I believe almost every developer with 10+ years of experience should be able to show some code he or she wrote. NDA is not an excuse. Almost every modern software highly depends on Open Source components. So it should be just a matter of time and experience to contribute the fix or improvement to some project. I could be completely wrong, but as a man who did hundreds of tech interviews during the last two decades, it was much easier to build the candidate profile if I could check the code he or she wrote. So while working on Open Source projects, you are definitely investing in the building of your public profile as a developer.&lt;/p&gt;

&lt;p&gt;Usually, you can't build a product without using various tools. Some of them can be free, and some of them can be commercial. The great benefit of working on Open Source projects is that a lot of companies with commercial products have special offers for non-commercial development. In the case of the "xq" utility, which is written in Go, I use &lt;a href="https://www.jetbrains.com/go/"&gt;GoLand&lt;/a&gt; IDE by JetBrains. I paid for it for several months but later tried to apply to their &lt;a href="https://www.jetbrains.com/community/opensource/"&gt;Open Source Program&lt;/a&gt;. They provided me with a license not only for GoLand but for the whole product pack! Another example is the &lt;a href="https://about.codecov.io/"&gt;CodeCov&lt;/a&gt; service. I want to track code coverage in an easy way to control the quality and ensure all major scenarios are covered by tests. CodeCov is quite expensive for commercial products (especially the hosted version, which costs ~50K USD), but it's absolutely free for Open Source projects, and this is awesome! If you need project hosting (e.g., for demo purposes), you may try to apply to the &lt;a href="https://www.digitalocean.com/open-source"&gt;DigitalOcean Open Source&lt;/a&gt; initiative, and there are other alternatives available. This is just a few examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting the Dots
&lt;/h2&gt;

&lt;p&gt;To summarize the article, here are some takeaways I hope you may find helpful.&lt;/p&gt;

&lt;p&gt;If you want to build an Open Source project, think about it in terms of the product, not the project, even if we are talking about the library. It should be easy to install and start using. The value for the end-user should be obvious from the first minutes of usage.&lt;/p&gt;

&lt;p&gt;Without marketing, people have almost zero chances to know about your cool project. It is surprisingly not so hard to start. And it's connected with your long-term motivation in more ways than you can initially think about. &lt;/p&gt;

&lt;p&gt;Your Open Source project should not become a tedious job. One of the ways to achieve that is to automate all routine work and enforce the policies. In the end, the release should be a joy, and reading user feature requests and bug reports should not make you cry due to the lack of essential details.&lt;/p&gt;

&lt;p&gt;Last but not least, working on Open Source projects is rewarding. Sometimes it's not so obvious, but if we are talking about the long-term perspective, it definitely is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft33a3naqo5dtj79pa0zo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft33a3naqo5dtj79pa0zo.jpg" alt="Behind the Scenes" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>programming</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
