Pages

Friday, October 7, 2022

DevOps-A-Palooza

Time flies, and this blog of mine is long forgotten. But my career and work are still going, so I decided to continue my blogs on a separate site - devops-a-palooza.

Please join me there!

Monday, January 15, 2018

Another code example

Yeah... Restart going a bit slow. But I have an excuse - New Year, Christmas and so on :)

Anyways, I am writing another short note - which is something. This time it will be another code example that I used for a technical code interview.

I think this kind of notes can be useful for people who are looking for a job. It is just a good checklist of what can be done and displayed. Unfortunately, for the last one we could not get a job or even a feedback. But this code example was accepted and it ticked all the checkboxes :)

So the task was to load a JSON object from the service and then filter, aggregate and display the data in some fancy way. There were no restrictions on tech stack or output, so I decided to go with the simplest one - console application. But to make it a bit spicier I constructed it in a way that developers will be able to extend or change those filters or aggregation functions very easy. By the way, the code is available here: github. There is a bit of explanation available in the readme file on github.

In terms of what is better to be shown:
  • There must be tests! You cannot ignore that nowadays if you are not writing tests you are doing something wrong. You can use whatever framework or tool you are familiar with - or you can play with something new.
  • When you are designing your app it is better to think about extensibility. Therefore IoC is a nice addition. And with modern frameworks it is not hard to set up at all. But you will show your familiarity with them and also your tests will be better!
  • Try and use the latest technologies. For example, if you are writing something for .Net - use HttpClient! 
  • Another good idea to show some exposure to functional programming. At least use LINQ :)
For me I also added my favorite idea - to mock the 3rd party services with a simple Node.Js service, as an icing on a cake.


Saturday, December 9, 2017

Restart!

Hi all,

After almost 1,5 years I decided to restart this blog. And not only to restart it, but rather start it in a new way with a new target!

First of all - the idea to restart it belongs to my wife and we will be writing articles together. Before I was writing articles that were more like a "gotchas" that I had, but now we want to focus this blog on a content that can be helpful for beginner developers. My wife did a bit of research and found that there are not so much of a content for them. Most of the articles are either too simple (write a hello world app) or just too strict (think of any todo app for any js framework). What we want to achieve is to provide people with a nice set of examples and small tasks that are closer to a real world (hopefully) and are more open for a change.

To do this we want to follow a certain pattern with blog articles. Each post will be either a task or an example app (and maybe, sometime other gotchas from me...). With tasks we will have a requirement and will outline a possible solution with a plenty of links to other resources with a lot of theory. That way developers that follow us will be able to build a project for each task and then compare it with what we build for the same task. Our solutions will be stored on git (e.g. https://github.com/ESKoptelova/codeexample-calculator).

We want to have our tasks to be aligned with a real world programming problems, so beginners might be able to solve the problem and put the code to their own git repository and use it as an example of their code for job applications.

I hope this works :)

Monday, May 2, 2016

SVN pristine copies

Hi! Probably all of us use some sort of the version control system nowadays. At my workplace we are using SVN (and Tortoise SVN as a nice user interface).

Interesting thing about the SVN is that is it using an internal folder (.svn) for its client-side operations. And one of those operations is to store pristine (unedited) copies of the working copy files. Overtime, this gets really big and can really take some space from your SSD drive – especially if you have multiple checkout folders pointing to different branches. It could fill your SSD really quick.

Sometimes there might be really old or big files stored. But you should not edit that folder directly. But you can do it using TSVN! Here is an example result:

And in order to achieve this you should use "vacuum pristine copies" during the cleanup along the deletion of unversioned and ignored files:

Tuesday, March 1, 2016

Developers and UX bugs

I work in a team of 6 developers, most of us are senior-level developers but we have 2 juniors too. And usually we could just happily develop our code and throw up some interface as we seen fit. But recently things changed and I wasn't that happy about it in a beginning.

My software manager approached us and told us that there were introduced heaps of bugs with the latest deployment. And those "bugs" were not the bugs in the codebase - instead they were UI and UX bugs - things were not loading extremely fast or were too hard to reach (for example if the button you have to click is too small). So after that we had to hold up our next deployment for 2 weeks and even start doing hallway testing! He is the boss, but initially that request was greeted with a lot of salt. After all we are simple developers - we want to write code and not to move buttons around the form. And we all know how the design will look like if developers do their best :) Another disturbing thing is that request came out from from blue. (Maybe because our manager was busy checking other projects he has not payed enough attention or just read some UX book...)

Anyway, we were not pleased and even now we can joke about that a lot - like "have you seen that show-stopper bug with wrong font size in that label?". But after some extra thoughts that request seems like something reasonable. After all we develop our application for users, to solve their problems and if they cant use it because the functionality is buried under those layers of crappy interface it is not working. It is almost the same - if the app is not working because of code bugs or if noone can understand how to use it. More than that - after the initial introduction to the systems users might just ignore the feature you have created because it is unusable; it can be really hard to turn them back to the feature later.

So what I am trying to say is that if your team doesn't have a separate designer/UX expert - every software developer should read at least few articles on most common UX/UI mistakes and try to improve the look and feel of your application. Those bugs can be even more dangerous then the regular ones. You can catch the bug in your code with unit test/integration test, but as you developed the feature - you cant really test the UI! You just know it from inside and wont notice the obvious defects. Hallway testing is the solution :)

Good luck and happy coding!

Wednesday, February 17, 2016

How to post the array of guids to the intranet

I thought that sending a bunch of guid to the server should be a trivial task. And from the client side it really is. Slightly harder if you want to build a small helper utility for that...

So the conditions of the problem. We have a Asp.Net web-application, running on the IIS server somewhere in the local network. All the methods are required a domain user authentication. It has a method that accepts posts like:

[HttpPost]
public void ReceiveContactIds(Guid[] contactIds) { ... }

There are a lot of possible solutions, after a quick googling I came got that stackoverflow answer.
Lets use the HttpClient solution! Yep... Unfortunately, it didn't helped as it is. First of all - how to send an array to the method and secondly it has no mention of the authentication process.

But those problems can be solved! That answer was not that helpful, but that one is much better. And we can use DefaultCredentials for current user. As for arrays - I just reused my experience with JavaScript and decided to send a StringContent with JSON. Here is my solution:

    private static void SendContactIds(IEnumerable<Guid> contactIds)
    {
        var credentials = CredentialCache.DefaultCredentials;
        var handler = new HttpClientHandler { Credentials = credentials };

        using (var client = new HttpClient(handler))
        {
            var content = new StringContent(
                     JsonConvert.SerializeObject(new { contactIds }),
                     UnicodeEncoding.UTF8,
                     "application/json");

            var action = "http://intranet.local/ReceiveContactIds";

            var result = client.PostAsync(action, content).Result;
            result.EnsureSuccessStatusCode();
        }
    }

Sunday, December 20, 2015

Knockout - example how to bind checkbox to radiobutton

I just want to share one small example of how to bind 2 collections together - using subscribe function. It will be done as a simple Asp.Net MVC application with Knockout + JQuery

That application solves a problem of selecting duplicates. Imagine that you have a database of customers, each customer has email address, name and id. It can later be linked to other entities like orders, but thats out of the scope of the current app. In order to manage that DB sometime you have to search for duplicates, e.g. "John Smith" with email "js@mail.com" should be the same person as "Johnny Smith" with the same email and it would be wise to let your users check that information.

So, somehow you managed to get all duplicates and want to show a form to the user to submit the merging process. I will be using that simple controller:

    public class DuplicatesController : Controller
    {
        public ActionResult Index()
        {
            var model = new DuplicatesCollectionViewModel();

            model.Duplicates = new List<DuplicateViewModel>
            {
                new DuplicateViewModel {ContactName = "Duplicate 1", ContactId = Guid.NewGuid(), Email = "mail1@mail.com", Reason = ""},
                new DuplicateViewModel {ContactName = "Duplicate 2", ContactId = Guid.NewGuid(), Email = "mail1@mail.com", Reason = "same email"}
            };

            return View(model);
        }

        public JsonResult GetAnotherDuplicate()
        {
            var number = new Random().Next(100);

            var result = new JsonResult
            {
                Data = new DuplicateViewModel
                    {
                        ContactName = string.Format("Duplicate {0}", number),
                        ContactId = Guid.NewGuid(),
                        Email = string.Format("mail{0}@mail.com", number),
                        Reason = "another one"
                    }
            };

            return result;
        }

        public void MergeContacts(List<Guid> duplicates, Guid originalId)
        {
            //do stuff
        }
    }

And model for the backend:
    public class DuplicatesCollectionViewModel
    {
        public List<DuplicateViewModel> Duplicates { get; set; }
    }

    public class DuplicateViewModel
    {
        public Guid ContactId { get; set; }

        public string ContactName { get; set; }

        public string Email { get; set; }

        public string Reason { get; set; }
    }
For the frontend I will use jquery and knockout.
There will be 2 models: DuplicateContact and DuplicatesViewModel. First one to store the information about every customer and the later one is the main model for the page. The view model has 2 observable arrays to store the all possible duplicates (from the server) and duplicates selected by user for the merge. Selected duplicates are bound to checkboxes via the "checked" binding.

Another property of the view model is the "primaryItem". It is bound to radiobuttons and also there is a subscription - to keep checkboxes and radiobuttons in sync. User can`t select primary item if it is not among merged items, so if this happens I want to include that item into the merged collection. Also that will flag item that it cant be excluded from that collection.

Well, here is the code for the page, I hope it is self-explanatory:
@model KnockoutRadiobutton.Models.DuplicatesCollectionViewModel

<div id="duplicatesForm">
    <table id="duplicatesTable">
        <thead>
        <tr>
            <th></th>
            <th></th>
            <th>Merge</th>
            <th>Primary</th>
        </tr>
        </thead>
        <tbody data-bind="foreach: duplicates">
        <tr>
            <td>
                <span style="font-weight: bold" data-bind="text: contactName"></span>
            </td>
            <td>
                <span data-bind="text: reason"></span>
            </td>
            <td>
                <input type="checkbox" data-bind="value: contactId, checked: $parent.mergeItems, attr: {disabled: isDisabled}" />
            </td>
            <td>
                <input type="radio" name="PrimaryContactRadioGroup" data-bind="value: contactId, checked: $parent.primaryItem" />
            </td>
        </tr>
        <tr>
            <td></td>
            <td style="padding-top: 0; padding-bottom: 0;">
                <span data-bind="text: email"></span>
            </td>
            <td></td>
            <td></td>
        </tr>
        </tbody>
    </table>
</div>

<div>
    <input type="button" data-bind="click: addNewDuplicate" value="Add another one" />
    <input type="button" data-bind="click: submitDuplicates" value="Send a merge request" />
</div>

<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/knockout-3.4.0.js"></script>

<script>
    function DuplicateContact(contactId, contactName, email, reason) {
        var self = this;

        self.contactId = contactId;
        self.contactName = contactName;
        self.email = email;
        self.reason = reason;

        self.isDisabled = ko.observable(false);
    }

 function DuplicatesViewModel() {
     var self = this;

  self.duplicates = ko.observableArray();
  self.mergeItems = ko.observableArray();

  self.primaryItem = ko.observable();
     self.primaryItem.subscribe(function (item) {
         //mark primary item as selected for merge
         var isForMerge = ko.utils.arrayFirst(self.mergeItems(), function(mergeItem) {
          return mergeItem === item;
      });
   if (!isForMerge) self.mergeItems.push(item);

   //mark new primary item
   ko.utils.arrayForEach(self.duplicates(), function(duplicate) {
       duplicate.isDisabled(false);
       if (duplicate.contactId === item) {
           duplicate.isDisabled(true);
       }
   });
     });

     self.getPrimaryContact = function() {
         var primaryContactId = self.primaryItem();

         var filter =  ko.utils.arrayFilter(self.duplicates(), function(duplicate) {
             return duplicate.contactId === primaryContactId;
         });


   if (filter.length === 1) return filter[0].contactId;

         return null;
  };

  self.addNewDuplicate = function() {
      $.ajax({
          type: "POST",
                url: "@Url.Action("GetAnotherDuplicate")",
                success: function(data) {
     var matchDuplicate = ko.utils.arrayFirst(self.duplicates(), function(item) {
         return data.ContactId === item.ContactId;
     });
                    if (matchDuplicate) return;

                    self.duplicates.push(new DuplicateContact(
                            data.ContactId,
                            data.ContactName,
                            data.Email,
                            data.Reason));
                }
            });
  };

     self.submitDuplicates = function () {
         var duplicates = self.mergeItems();
         var primary = self.getPrimaryContact();

         if (duplicates.length == 0) {
             alert("You have to select duplicates!");
             return;
         }
         if (!primary) {
             alert("You have to select the primary contact!");
             return;
         }

         $.ajax(
                {
                    type: "POST",
                    url: "@Url.Action("MergeContacts")",
             data: {
                 duplicates: duplicates,
                 originalId: primary,
             },
             dataType: "json",
             traditional: true
         });
     };
 }

 $(function () {
        var viewModel = new DuplicatesViewModel();

        @foreach (var duplicate in Model.Duplicates)
  {
   <text>
    viewModel.duplicates.push(new DuplicateContact(
     '@duplicate.ContactId',     
     '@duplicate.ContactName',
     '@duplicate.Email',
     '@duplicate.Reason'));
     </text>
  }

     ko.applyBindings(viewModel);
    });

// allows debugging of dynamically loaded scripts
//# sourceURL=Duplicates.js
</script>