Updates from December, 2020 Toggle Comment Threads | Keyboard Shortcuts

  • kmitov 10:15 am on December 16, 2020 Permalink |  

    FabBRIX demo 

    FabBRIX JUNGLE LIFE – Gorilla in 3D building instructions
     
  • kmitov 10:10 am on December 10, 2020 Permalink |
    Tags: , ,   

    Send array params to a rails server from a JavaScript code – URL and URLSearchParams 

    The goal was to filter building instructions on buildin3d.com by brand. We had to parse and create the URL on the client so I played around with URL and URLSearchParams (again). I will try to summarize the implementation here in the hope that you could help understand how URL and URLSearchParams work and how to use them with a Ruby on Rails app.

    Rails Server side request with array param

    The server accepts a request in the form:

    https://platform.buildin3d.com/instructions?in_categories[]=1&in_categories[]=2&in_categories[]=13

    This means we could pass an array with the ids of the categories. The result will return only the 3D assembly instructions that are for Brands in these categories.

    How to send the request on the client side

    On the client side we have an <ul> element with some <li> elements representing the categories.

    The brands filter is on the left.

    When we click on the brand we would like to add the brand to the URL and redirect the user to the new URL.

    So if the current url is

    https://platform.buildin3d.com/instructions?in_categories[]=1&in_categories[]=2

    and we select a new brand I would like to send the user to

    https://platform.buildin3d.com/instructions?in_categories[]=1&in_categories[]=2&in_categories[]=13

    How to add array params to the URL with JavaScript

    Here is the whole code of the Stimulus JS controller

    import { Controller } from "stimulus";
    
    /**
     * This is a controller used for filtering by brands [categories] on the materials index page
     *
     * @author Kiril Mitov
     */
    export default class extends Controller {
      static targets = ["tree"];
    
      connect() {
        console.log("connect");
        const scope = this;
        this.setFromUrl();
        this.treeTarget.addEventListener("click", e => {
          e.preventDefault();
          const li = e.target.closest("li");
          const input = li.querySelector("input");
          input.checked = !input.checked;
          scope.goToNewLocation();
        });
      }
    
      setFromUrl() {
        const url = new URL(window.location);
        const categoryIds = new URL(window.location).searchParams.getAll("in_categories[]")
    
        Array.from(this.treeTarget.querySelectorAll("li[data-category-id]"))
          .forEach(li => {
            const input = li.querySelector("input");
            const selected = categoryIds.indexOf(li.dataset["categoryId"]) != -1
            input.checked = selected;
          });
      }
    
      goToNewLocation() {
        const url = new URL(window.location)
        const searchParams = url.searchParams;
        searchParams.delete("in_categories[]");
        Array.from(this.treeTarget.querySelectorAll("li[data-category-id]"))
          .filter(li => li.querySelector("input").checked)
          .forEach(li => searchParams.append("in_categories[]",li.dataset["categoryId"]))
        searchParams.sort();
        window.location = url.toString();
      }
    }
    

    There are a few important things in the code

    Opening the page on a new location

    window.location = url.toString()

    This will open the new page for the user

    Adding the selected brands to the url search query

    We listen for an event of click from the user and we get a list of all the checked brands.

    goToNewLocation() {
        const url = new URL(window.location)
        const searchParams = url.searchParams;
        searchParams.delete("in_categories[]");
        Array.from(this.treeTarget.querySelectorAll("li[data-category-id]"))
          .filter(li => li.querySelector("input").checked)
          .forEach(li => searchParams.append("in_categories[]",li.dataset["categoryId"]))
        searchParams.sort();
        window.location = url.toString();
      }

    First we delete the param “in_categories[]”. We use URLSearchParams.delete. This removes the param from the searchParam and if we then call .toString() we would receive the new search query without this param.

    Then we call this.treeTarget.querySelectorAll to filter all the checkboxes and then only the check once and we append “in_categories[]” param for every selected checkbox. This is what the server requires.

    searchParams.append("in_categories[]",li.dataset["categoryId"])
    

    As a result with have the query

    https://platform.buildin3d.com/instructions?in_categories[]=1&in_categories[]=2

    Because we use only URLSearchParam.append and URLSearchParam.delete all the other params are still in the search query.

    As a summary:

    We have a Ruby on Rails server that accepts an array param and we form this array param in a Stimulus JS controller. We use URLSearchParams method to append and delete params, as this will preserve the other params that are already in the url.

     
  • kmitov 9:39 pm on November 28, 2020 Permalink |
    Tags: , , , ,   

    Why we should never clear our DB before/after running specs. 

    One common “mistake” I’ve seen a couple of times is to clean the Database before/after specs are run. It seems to be a common practice with reasonable arguments. I think this is a bad idea. Here is why and what we should do instead.

    Why is the DB cleared before/after the specs

    When running specs that need access to a DB we might have to create a User or an Article or a Project model, then connect them in a certain way and test the business logic of our spec. After the spec is finished it is not wise to delete these objects from the DB directly in the spec. Sometimes it takes additional time, sometimes it executes additional logic. In most cases you don’t clear the DB after each and every spec.

    It is a good idea to clean the db before all the specs or after all the specs if they are successful. In this way we reset the DB only once, it saves some time and is much cleaner because you can plug in this behavior if you want to.

    Why the DB should not be cleared before/after the specs

    The simple answer is that our code will never, absolutely never work on a clean db in a production. If we have a test procedure that runs the specs against a clean and empty db they might pass when the db is clean. But what use do we have from code that could work in a clean environment, but could not work in a real production environment. The answer is – non.

    We don’t clean our db before/after each spec. In this way we’ve been able to track some really nasty bugs. Like slow queries that are slow only when you have too many users. Other cases involve special relations that are built in time. Like users that are part of an organization and the organization was once having one check for uniqueness of the user and now it has another check. Because the db is not cleared every time we make sure that it is properly migrated with all the needed migrations.

    We found out that a 7 years out test db that is not cleared is closer to a 7 years old production db.

    The test db is not the production db

    The test db is not the production db. It might have the same scheme, that is for sure, but the amount of data in them and the complexity of this data is different. What we need is code that could run on a production db. There is no use of any code that could run only in test environment.

    So here is what we do:

    We export the production db, we change some data like user emails, names and any other sensitive data and we import it as a test db. We run the specs on this db.

    In this way we make sure that the code could actually run on a real db before deploying it.

     
  • kmitov 11:05 am on November 16, 2020 Permalink |
    Tags: , animations, ,   

    We got featured in the BABYLON JS 4.2 release video 

    About two year ago when we started working on delivering 3D instructions and playing 3D animations and visualizing 3D models in the browser. Little did we know than that with version 4.2 babylon js will feature us in their release video. Thank you BABYLON theme.

    I wrote an article on BuildIn3D.com – https://buildin3d.com/blog/buildin3d-featured-in-the-babylon-js-4-2-release-video/

    Here is the release video for all to enjoy.

     
  • kmitov 5:54 am on October 21, 2020 Permalink |
    Tags:   

    How I became one of the top JavaScript developers I know of? 

    TL; DR; How do you measure how good you are? – my highly unscientific measurement is around “when others start asking and listening to you” and when you “start delivering faster and better results”. I became really good in three steps – start solving a really hard problem, ditch the JavaScript dependencies coming from the community and pack everything.

    Context

    If you’ve been in the field of Software development and you are already a good developer and you’ve used but are generally avoiding JavaScript (because reasons), then this article is just for you. It sounds oddly specific, but in the same time I am constantly seeing many people in this situation. This was me a few years ago so I hope it could help you.

    In about 1.5 years I’ve become one of the best JavaScript developers I know of. The moment I personally started delivering better results than the much more experienced contractors I was hiring, while I was also managing our organization, I knew I have moved a long path.

    What was wrong with JavaScript?

    Nothing in particular and everything. We’ve seen countless articles from well read and published software developers over the last two decades about why JavaScript was “bad”. I am not going to get philosophical here, because I found out that they are all true and yet what stands behind the notion of JavaScript in the beginning of the third decade of the 21 century is still a very vibrant and live community achieving incredible results.

    Step one. Find a difficult real-world problem.

    My problem was to build an event-driven plug-in architecture in JavaScript while incorporating and refactoring legacy code that became one of the extensions. This was version 6 of our Instructions Steps Framework delivering the 3D assembly instructions at buildin3d.com. We are currently working about a hundred extensions.

    Finding a difficult problem could really focus you. It was not difficult from an algorithmic or mathematical point of view. It was not difficult as a new blockchain or AI or another emerging technology. It was difficult from an architectural point of view. How do you build a framework for plugins that live in a browser and that must incorporate some legacy behavior.

    I guess finding a difficult problem applies to learning all kinds of technologies.

    Step two. Ditch the dependencies. Go vanilla.

    The community is large. We have JS in the browser, in native apps, on the server. We have JS everywhere. The entry barrier is extremely low. You can build a new JS tool today, publish it on Reddit and have hundreds of people trying it tomorrow. In half a year your have hundreds of npm packages that are dependent on you, you change something and the whole npm is broken. This happens more often than you might think.

    Most of the solutions that could be found on the internet are outdated or plain wrong. Most of the solutions are not about the problem that is addressed. I guess because of the size of the community there are so many “answers and solutions” on the internet around – “take this library and do this call”, but these are not solutions for the case you are working on and are solutions that surely will not work in a year or two.

    Example. I’ve never in my live done something like:

    $ mv --version 1.2 source.md target.md

    This is a command for moving the file source.md to target.md where I’ve specified the “version” of the mv that is to be used. Does this sound familiar? No, of course. I am not sure even if it is possible to specify the version of mv to be used. I had an enormous difficulty explaining this to the maintainers of some of the popular tools used in the JS world and they did not agree with me. (No link here because I don’t want to point fingers.)

    I can not forget an interesting lecture by Linus Torvalds that I once watched where he was:

    You never break libraries ABI (Application Binary Interface). This is the thing I am very serious about in Linux Kernel. You don’t break ABI. You support it.

    But that is not how much of the JS community and in fact many other communities think.

    So I ditched the dependencies. I went full scale vanilla JS. I was not even depending on the DOM for much of the implementation and the things that were dependent on the DOM and some of the WEB APIs were extracted into proper extensions. Without any dependencies I had to do more work, and I would admit I had to write a tree traverse and observers on my own which I never expected I would have to. But at the end this ends up to be trivial implementations extracted to a proper extension that we could easily change with a new more well maintained library (when I find one).

    Step three. Pack and Go.

    Webpack it is. There are others. I’ve used many tools and I found out it is extremely important to pack your JavaScript properly and to understand the whole process. So look at the tool for packaging and try to develop the JS into different modules but pack them all into a single file that you deliver to the browser. The amount of issues you could face with this is really astonishing.

    Step four. Debug a jQuery problem.

    It took me weeks to migrate one of the platforms that we are running from jQuery 1 to jQuery 3.5. The amount of work required to completely remove jQuery dependencies was still too much so we decided to first migrate it and probably later remove it. Our code was not dependent on jQuery1, but some of our dependencies were. I have a friend that is now migrating from jQuery 3.4 to jQuery 3.5. I call these things the “jQuery problem” – as the library made it so easy to approach and solve different problems it was used in all kinds of unintended ways that it was never designed to. As the answers in blog posts and internet forms were based mostly around jQuery there was a decade or two with a lot of copy/pasting going on. Debugging such hard problems could be an eye opener.

    Conclusion

    A lot of other things matter – like choosing the right tools. But in retrospect these were the things I learned most from.

     
  • kmitov 7:07 am on September 30, 2020 Permalink |  

    ApplicationController.render – one more way for a CSRF token to annoy you. 

    CSRF token and ActionController::InvalidAuthenticityToken” could be some of the most annoying issues in a rails application. Here is one more way you could get annoyed, and it is rarely the rails fault.

    ApplicationController.render

    The method allows us to render rails partials outside of the views and controllers. It is useful for things like rendering a view in a delayed job. There is no request context there, no session and yet sometimes you would like to render a view in a job.

    I think it was introduced in Rails 5 and we’ve been using it here and there.

    ApplicationController.render for a json response.

    We are using Datatables-net and we generate a JSON response that contains some HTML that should be visualized to the user. The HTML is a button_to for a delete form. In the table for our groups we had a remove action. This remove action is actually a form for an HTTP delete request.

    The form has a token

    It turns out that the token generated in the context of ApplicationController.render is invalid. There is no session passed to ApplicationController. We should have just used ‘render’ – the method available in the views.

    The correct fragment was

       def generate_remove_button_for record
    -    rendered_string = ApplicationController.render(
    +    rendered_string = render(
           partial: 'shared/fc/admin_unify/table_actions',
    +      formats: [:html],
           locals: { 
             links: get_edit_delete_actions([*@namespaces, record]),
             id: record.to_param
    
     
  • kmitov 1:03 pm on April 22, 2020 Permalink |
    Tags: , jquery, , , , ,   

    Rails 6 + webpacker + jquery + sprockets + jquery plugin (fancetree) 

    So you might be in the process of migrating to webpacker. This means your sprockets should continue working. This is difficult. Sprockets wants jquery available in the view and you don’t have jquery available in the views. You have it in the webpacker packs.

    These things won’t work

      <script> 
        $("element_id")
      </script>

    jQuery is only available to the PACKS it is not available to the VIEWS.

    But there is a solution – expose-loader

    Here is how to setup jquery to be available to the views in sprockets app that you are migrating to rails 6. I am starting from the previous article were we set up the project from 0. – https://kmitov.com/posts/rails-6-webpacker-yarn-fancytree-less/

    $ yarn add expose-loader

    Add configuration for exposing of jquery

    // config/webpack/environments.js
    const { environment } = require('@rails/webpacker')
     
    const less_loader= {
     test: /\.less$/,
     use: ['css-loader', 'less-loader']
    };
    environment.loaders.append('less', less_loader)
    
    +
    +const webpack = require('webpack')
    +// this makes jquery available in all pack and you don't
    +// have to import or require it each time 
    +environment.plugins.prepend(
    +  'Provide',
    +  new webpack.ProvidePlugin({
    +    $: 'jquery',
    +    jQuery: 'jquery'
    +  })
    +)
    +
    +// this exposes jquery to be available in the views
    +// <script>
    +//   console.log($('#tree'))
    +// </script>
    +environment.loaders.append('expose', {
    +  test: require.resolve('jquery'),
    +  use: [{
    +    loader: 'expose-loader',
    +    options: '$'
    +  }, {
    +    loader: 'expose-loader',
    +    options: 'jQuery',
    +  }]
    +})

    Also expose fancytree

    // config/webpack/environments.js
    ...
    +// this exposes fancytree to be available in the views
    +// <script>
    +//   console.log($('#tree').fancytree())
    +// </script>
    +environment.loaders.append('fancytree', {
    +  test: require.resolve('jquery.fancytree'),
    +  use: [{
    +    loader: 'expose-loader',
    +    options: 'fancytree'
    +  }]
    +})
    

    And you are done.

    How in your views you could do:

    <script>
      console.log($('#tree'))
      $(function(){
        $('#tree').fancytree({
          extensions: ['edit', 'filter'],
          source: [
            {title: "Node 1", key: "1"},
            {title: "Folder 2", key: "2", folder: true, children: [
              {title: "Node 2.1", key: "3"},
              {title: "Node 2.2", key: "4"}
            ]}
          ],
        });
        const tree = fancytree.getTree('#tree');
        // Note: Loading and initialization may be asynchronous, so the nodes may not be accessible yet.
      })
    </script>
    

    Fancytree as a jquery plugin is working in rails 6 views and is available also to sprockets compiled files.

     
  • kmitov 4:43 pm on April 1, 2020 Permalink |
    Tags: documentation,   

    This code is: ‘BAD’. This code is: ‘GOOD’ 

    This article is part of the series [Everyday Code].

    TL; DR;

    When providing examples in developers documentation always add the bad example, but make it stupid clear that “this is a bad example and it should not be done like this, while this is a good example and it should be done like this!”

    Too long; but I will actually read it.

    We are copy/paste developers. We are fighting challenging problems on tight schedules with complex frameworks running on enormous stacks and we rarely use tools designed with ergonomics in mind. When we stumble on a problem we go for an internet search, copy the first result that looks like a solution and continue. (this is also my main mode of work with nginx. I love it, but it is always so much work to get this assets through a proxy, and this server was even designed for this.). Or AWS API…

    Yet, we somehow manage to deliver software platforms and most of them are in a running state. Brilliant.

    One thing that we are missing in the documentation is of course the understanding of the “why”. Why things are implemented like this? What were the considerations? Why it should not be implemented in different ways? You get this in a book, but you don’t have it in the documentation.

    Most of the time of course we don’t have the time to enter into the details, but I would make the argument that the more we understand the underlying problem, the more fun it is and the more satisfaction that we can get from work. Because most of the time we just can get not satisfaction. (no reference intended, but here it is – https://youtu.be/EU1t1PqASuQ?t=47)

    But this brings us to the problem. When developing a tutorial for how to use your framework should you include the bad examples along with the good?

    Should you include the bad examples in the documentation?

    This was an issue in the early stage of our Instruction Steps Framework developed at Axlessoft.com. As a proper framework it has documentation. As it is a framework it puts some boundaries and rules on what you should do. But we’ve tried to go one step further and describe why we are doing it like this.

    I’ve found it rewarding to include information about why specific decisions were taken not only what are these decisions.

    Example 1 – we’ve decided not to have a central object model on which all extension are based. A central object model would simplify a lot of things and is a lot easier for many developers, but it is less flexible.

    Example 2 – we’ve decided that certain event methods could return Promises, but could also not. Now why would you do this to yourself is a really good question?

    In these two and other examples I have taken the time to include proper documentation and tutorials on why we are implementing a feature like this. But I failed at one place. I was including both the bad and the good examples in the documentation.

    Looks at the example below for iterations from the previous article. This is an example for iterating over trees.

    anArray = node.getDescendants();
    // here you have all the elements of the tree in anArray
    
    // loop over the array
    anArray.forEach((a)=> {
       if(a.name.includes("1")) {
          print a.getDescendants().length
       }
    })

    Here is an example without getDescendants()

    anArray = []
    rootNode.traverse((node)=> {
       anArray.push(node)
    })

    See what I did here. If you came to this article looking for ways to iterate over trees it is completely unclear what is good and what is bad. Writing it right here in the text as “the first example is bad”, “the second example is good” won’t help. Who is even reading this text? So you arrive at the article, get the first iteration that is “bad iteration”, copy it and paste it in your code.

    The point is – it is valuable to include the bad examples as they give people the understanding why the good examples are good. But by including the bad example a large percentage of your readers will just copy paste the bad example and continue.

    My solution

    Mark your examples clearly in the code as “GOOD” or “BAD”. Not in the text. We are developers, we don’t read text.

    // BAD EXAMPLE - because it uses a lot of memory
    anArray = node.getDescendants();
    // here you have all the elements of the tree in anArray
    
    // loop over the array
    anArray.forEach((a)=> {
       if(a.name.includes("1")) {
          print a.getDescendants().length
       }
    })

    Again some text that no developer is ever reading, as it is in between the examples.

    // GOOD EXAMPLE
    anArray = []
    rootNode.traverse((node)=> {
       anArray.push(node)
    })

    See how clear this is. I dare any developer to now copy the bad example in their code. Hope this approach could help you in time of need.

     
  • kmitov 6:17 pm on March 31, 2020 Permalink |
    Tags: algorithms, , , tree   

    Everyday Trees in Computer Science – [Everyday Code] 

    The goal of this article was to equip developers with everything they need to know about trees to use Instruction Steps Framework (IS) and Instruction Converter (IC) that we are currently working on in Axlessoft. While developing the article for internal use I realized that these are things that every developer should have under their sleeve to successfully use tree structures in their day to day tasks.

    image of a tree that will make the article more interesting. Not a real programming tree

    Some of us might be tempted to revise the heavy Knuth books when it comes to solving the next tree problem. But generally we don’t need to do it. You know the meme:

    • First year Computer Science – “algorithms, data structures, etc …”
    • Second year – “functional programming, object oriented, etc…”
    • Last year – “cryptography, network programming, design of languages, etc
    • First day at work – “move this button to the left”

    So let’s start with a few things you need to know day to day.

    You will see “parent->children” more often than “node->left,right”, eg. binary trees.

    If you have a structure of parent and children this is a tree structure. You have three scenarios:

    1. The parent knows about the children and the children know about the parent. There is a parent.children and child.parent
    2. The parent knows about the children, but the children do not know about the parent. There is parent.children, but there is no child.parent
    3. The parent does not know about the children, but the children know about the parent. There is no parent.children, but there is child.parent

    In many books, algorithms and generally articles you will have people talking about Binary Trees. Now, it might be because of my line of work, but:

    1. I can not remember the last time that I had to properly work with a binary tree – something more than “just find this node”.
    2. For every time I have to work with “binary trees” there are at least 1024 times I have to work with “parent->children” structures.

    Yes, it is the same. But it is also not the same.

    Take away:

    All the three cases represent a tree. It is best to have case 1, but this is not always possible. You will deal with “parent->children” more often than with “left node”, “right node”.

    If there is a library for it, use a library.

    This should be a no-brainer, but a lot of times we like to code a few iterations here and there and develop things for basic iteration over a tree. Just don’t. If there is something tree related that you can not find a library to do for you in about 5 minutes internet search, then you are doing something worth of a Phd or you are searching with the wrong queries (or you are trying to do something stupid!).

    There are of course exceptions. Because when we, as great developers, implement Pre and Post order iteration we are doing it better than anyone else. So this exception is acceptable. Of course.

    Iteration -Pre, Post, In and visiting a node

    Visiting a node means that you are processing it. For example printing to the screen.

    There are a few basic ways. Just search for them on the internet. There are a lot of really nice resources of how you iterate over tress. The basic ways are:

    1. LRN – Left Right Node – first you visit the left child, then you visit the right child, then you visit the node. But as we say this is the theory. In a real life scenario with “parent->children” you visit children[0]..children[n] and then the parent
    2. LNR – Left Node Right – How do you do this for “parent->children” I don’t know.
    3. NLR – Node Left Right – first the parent then children[0]..children[n]
    4. NRL – Node Right Left – first the parent then children[n]..children[0]
    5. RLN – Right Left Node – first children[n]..children[0] then the parent.

    Avoid recursion

    Yes, you have trees. But avoid recursion. Use “traversers”. Almost any reasonably useful library has a “traverser” support. The idea is simple – somebody else is doing the recursion for you and this somebody else is the library.

    In pseudo code:

    rootNode = ... 
    rootNode.traverse((node)=> {
       print node
    })

    The idea is simple. You have a rootNode. It has a method ‘traverse’ that will traverse the whole tree, iterate, recurse, do a lot of magic, but at the end it will traverse the whole tree and will call the “print node” for every node in the tree.

    There is no recursion or iteration in your code. There is just traverse. Sometimes it is called “visitor” ( Visitors pattern, Gang of four, things like this). I kind of preferred “visitor” in early stage of my software development career, but now I more often call them “traversers”. Traversers are visitors, but not all visitors are traversers from my point of view. Would be happy to talk about this over a beer. Anyone..?

    Take away:

    If you are using a library that does not have traverse you should seriously consider using another library.

    Avoid ‘getDescendants()’ and getDeepChildren()

    Yes. These are some libraries that try to help. Imagine that you nave:

    node 1
      node 1.1
        node 1.1.1
      node 1.2
        node 1.2.1
        node 1.2.2
      node 1.3

    You need to get all the children of node 1 and all of their children and you need this in an array. Why? Because reasons. This is the feature request. There are libraries like BABYLON.js that try to help. They offer a “getDescendants()’ method. You can call:

    anArray = node.getDescendants();
    // here you have all the elements of the tree in anArray
    
    // loop over the array
    anArray.forEach((a)=> {
       print a
    })

    This method saves a lot. You do not need a “traverser”. You don’t need to know anything about order. You need all the elements of the whole tree so get an array. But there are drawbacks.

    1. It could be a large tree. So you are creating a copy, but this time in an array and memory and user experience are expensive.
    2. Even if it is not large you are still creating an array. Do you really need an array or you could just traverse the whole tree and do your job without previously creating an array?
    3. If you have to call getDescendants() more then once, you should refactor your code.

    Consider the following:

    anArray = node.getDescendants();
    // here you have all the elements of the tree in anArray
    
    // loop over the array
    anArray.forEach((a)=> {
       if(a.name.includes("1")) {
          print a.getDescendants().length
       }
    })

    In the example above we would like to print the number of descendants for each node that includes the string “1” in its name. How many times is the tree traversed? How many arrays are created? The answer is “a lot”. You traverse once for the rootNode and then for every other node again and you create new arrays.

    Take away:

    If there is a helper method it is probably giving you, the developer, something, but it is at the expense of the user experience – more memory, slower execution. Consider the implications of using helper methods. They might help you, but are they helping the user using your software.

     
  • kmitov 7:47 am on February 21, 2020 Permalink |
    Tags: , ,   

    Feedback could come in any form, not just the one that makes it easy to accept it – [Everyday Company Culture] 

    Culture is how we do stuff around here. Read more about the [Everyday Company Culture] series.

    Today’s resolution. TL;DR

    Feedback from the team could be returned in any form, not only formally, not only verbally, not explicitly, but by the day to day mistakes that we make. Accept all kinds of feedback. Don’t be a “douchebag”.

    Today’s case

    There was a requirement for everybody to write the text “Result” as a first line in the comment of his/her work when marking a task as completed. People were following the rule, but writing “reuslt”, “results”, “Done:”, “Restult”, “Release:” and many others, but were not writing “Result”.

    Instead of pushing for everybody to use the right “Result” at the end we ended up with a list of possible “results” that team members could write as the first line of their result description. It turn out to be pretty funny to add new results and as of today this is the list of all the possible values, accepted by the platform.

    possible_results = ["result", 
      "results", 
      "result:", "results:", #These are added because of Ivan
      "rrrrrr", 
      "rrR", 
      "blabla result", 
      "result blabla", 
      "Special Result Line", 
      "The Result:", 
      "Release:", 
      "Release", 
      "Take this result Kiril, I hope you are having a great time",
      "Resutl", # this one is added because of Mihail
      "Restult", # this one is added because of Mihail
      "Ready:", # this one is added because of Mitaka
      "Ready", # this one is added because of Mitaka
      "Done:", # this one is added because of Aleks
      "Done", # This one is added because of Aleks
      ]

    These results are accepted in any form of case – upper and lower. We have a team member that submits his results as:

    TaKe ThIs ReSuLt KiRiL, i HoPe YoU aRe HaViNg A gReAt TiMe

    Which is arguably, very funny.

    Root cause

    Feedback was not accepted. 

    We as a team all accepted that we should have “Result” of the work and we should have a description what is actually the result. Generally the result is different from the goals of the task. The goal is “develop a feature”. But the result is “visit this URL to see the feature”.

    We were just making mistakes when marking the result and I was pushing for the use of the correct word which is:”Result”. Nobody returned an explicit, verbal feedback – “I think we should make the result with ‘Done:’, because I don’t like writing ‘Result:'”. Nobody said this. We all understood that there should be a rule and we should follow the rule. But we were just making mistakes.

    Full Story

    I was trying to establish a rule in the team – when a piece of “work” is completed you must provide a short description, URL link, commit, picture/video of the new feature. This makes it extremely easy to generate release notes that we make public to the client. Only rule was

    “When you submit your the description of the result the text must start with ‘Result’.

    Something like this will be enough

    Result

    We have implemented a new feature that shows a progress bar when downloading the assembly instruction.

    Here is how it looks like

    https://s3.amazonaws.com/fllcasts/content_pictures/pictures/000/003/479/4d609c2301bbe94f7a1c9825b7a2d85275c18537IS-Showing-Progress.png?1581586917

    After the result is provided we use different internal tools and I could easily generate public client Release Notes for our products and platforms. Examples at: https://www.axlessoft.com/release-notes or http://fllcasts.com/release-notes

    Sounds easy. Only thing you have to do is start your description with “Result”, because other values could be “time track:”, “Code Review”, etc. It took me weeks before I finally realize that we should not be strict on the value “Result”. We should be strict on the behavior that everybody should write a result, but if you happen to make a mistake and write “resTult” – that should not be a problem. We got you on the platform end, accepting different input values for result. Does not matter how you call it, as long as it is there.

     
c
compose new post
j
next post/next comment
k
previous post/previous comment
r
reply
e
edit
o
show/hide comments
t
go to top
l
go to login
h
show/hide help
shift + esc
cancel