The forum post can be found here. I’m glad to say a lot of these pains have been fixed, including:
- New filters in the side menu and the ability to create custom filters. Documentation
- Promotions loading incrementally, they now load first when you expand the Campaign
- Instead of loading all promotions in the prioritization view, the client loads them in chunks, depending on the window size
- An added search bar to be able to find discounts
While all of that is really good, there are still some things that could be improved and some areas that still aren’t very good UX. The interaction design bits from my old post still applies, such as single clicking on a Campaign does… something? It gets a border. Apart from that, nothing, you need to double click to expand the child promotions, which is to me (and many others I’ve seen) a bit hidden.
But today I’ll focus on the prioritization view, which I think is in most crucial need of some fixup. For those who do not know how to find this view, as it is not extremely clear, it’s by clicking the small icon in the upper right corner while at the Campaign list view.
So, here goes.
Important note: I work with clients that work with Promotions a lot. All the screenshots and measurements in this post will be from a specific customer that has 1400 inactive and active promotions. While one could argue that there are a lot of inactive that could be deleted, by the nature promotions works for purchase orders, you don’t really want to change/delete them too much. The environment is Commerce 11.2, so it’s not the latest. However I couldn’t find any relevant changes in the changelog regarding this view. But there might be some things that’s already fixed. Did I hear a Quan yelling “always update to latest version” from a rooftop afar just now? :)
Searching for promotions
When an admin wants to go and change priority of a specific promotion, to maybe solve a prioritization issue with another promotion, they will struggle if they have ~80 promotions or more, and the struggle scales with the number of (inactive+active) promotions.
While the Campaign list view has gotten a search bar implemented, the prioritization view has not. However, you could argue that you could simply search in page via the browser (Ctrl/Cmd+F) which would be handy if it wasn’t for virtualization of the list.
With lack of a better name, I’ll call it this throughout the post and since I’ll refer to it several times, I thought I should explain what it is.
To make the list more performant in the browser, i.e. while scrolling, drag’n’dropping, etc, not all items in the list are rendered to the HTML DOM. You’re only looking at a window of the list. That window is (I believe, haven’t looked at the code) roughly the amount of items that fits in 150-200% of your screen height. The amount of items in the window won’t change, but while scrolling you still get normal scrolling behaviour. So while scrolling down, the items in the top of the window are removed and items in the bottom are added, giving the illusion that the full list is there but in fact it’s virtualized in memory. Performance thinking, I like it! However, some thinks needs to be dealt with differently when having this virtualized list approach.
Seeing as only a portion of the list is actually in the HTML DOM, only that portion is searchable by the browser search. If you have 1400 promotions and your job is to prioritize promotion X at least over promotion Y, that job becomes quite hard. People are creative however, so the people who work in this has some workarounds including putting the screen in portrait mode and zooming out as much as you can, so the items in the current “window” increases a lot. Or abusing the fact that if you change any priority of any promotion, the virtualized list feature is removed and the entire list is written to the DOM.
Dragging and dropping performance
This picture isn’t very nice to look at when working with browser performance. It is not great when the browser gives you warnings in the log that a click event took more than 5 seconds. While this happens, the entire UI is frozen, you can’t click or interact on anything. This is a screenshot when dragging and then dropping, the mouse up event being when you drop the promotion. The virtualize implementation of that list should generally take care of that so it isn’t expensive to modify/interact with the existing list items, but somewhere somehow it doesn’t. Dragging items when the total number of promotions is big is not a very pleasant experience, regardless of how many list items the browser renders in the window.
Another thing with dragging when it comes to the big list that would still be something troublesome even if the frame rate would stay at 60, is that if you have to prioritize a promotion very differently. For example, when you create a new promotion, that promotion is prioritized furthest down in the list. If you want this promotion to have the highest priority, you will have to drag and drop very far. For me, a drag and drop feature thrives when you can drag and drop the items within the viewport. Whenever you have to work with lists that go outside of the viewport, maybe drag and drop isn’t the best method to move items in that list.
Dropping an item scrolls you to the top
Another thing adding to the bad experience drag and dropping discounts in the prioritization view is that whenever you drop an item after dragged it, after the couple of seconds waiting for the mouse up event to be handled, you are scrolled to the top. If you for example are moving a promotion from #655 to #653, you will have to do a lot of scrolling to verify the changes you just did.
Dropping an item kills the virtualization
Whenever you change the prioritization of promotions, just dropping without saving, the entire list is rendered to the HTML DOM. The virtualization feature is gone. I’m sure there was a reason for this, but regardless the result is that the browser window becomes incredibly laggy. Drag and dropping even more promotions in this phase, you’ll have a bad time. This in combination with being scrolled to top and not searchable/filterable, makes it even harder to find the promotion you just change.
To somewhat illustrate the struggle we can take a look at a profiler to see what we can produce.
(Bug) Entire list is sometimes not rendered
When testing around with this interface (with a large number of items, as stated before) I found that sometimes when the entire list is printed to the DOM (as described above), not the entire list is actually printed. It feels like the renderer just gives up someway along the line and returns whatever items it has processed. I couldn’t reproduce this very often on my work computer with my 2560x1600 screen and powerful processor, but on a less powerful ultrabook with 1920x1080 (125% zoom, Windows) I could reproduce it all the time. Easily enough:
- Go to the prioritization view with a quite large promotion list (>1000?)
- Note the amount of promotions
- Reorder any promotion
- Wait for the UI to become responsive
- Note the amount of promotions (can be less than previous count)
The list is reloaded on successful save
Whenever you have made a change and clicked on the blue save button and the server responds with OK, the entire list is reloaded again. This means the browser has to remove all the DOM elements (without serialized list, since that is removed if you reorder any promotion), show the “Loading discounts” text and as soon as the server request comes back it has to write to the DOM again. Again, there might be some technical reason for this but the user experience is the one taking the fall.
Add exclusions popup freezes the UI while loading
Whenever you want to add some exclusion, you click the plus next to a promotion and get prompted with a list of which promotions you want to add to be excluded. When testing this with the big amount of promotions, I had to wait several seconds before I could see the popup.
If you take a look at the image (where I’ve censured the promotion names by background-color: black), it took a whole 10 seconds for the UI to become responsive again, we managed to render 1 frame in 9822ms, quite far from the goal of 1 frame per 16.67 ms (60 fps). And when I looked at a trace what the browser was doing, I found a very expensive forEach loop that did tons and tons of work in a very big iteration, presumably all the promotions.
The following I could reproduce every time when I wrote it, but I can't anymore. Will keep it here and keep looking at it.
Dragging and dropping “across windows”
Another thing I found when playing around was that whenever I moved a promotion just a few steps, within the currently rendered window, the request payload when clicking save will only contain 1-2 promotions. Presumably the promotion moved and maybe the promotion in the previous order. However, when dragging a promotion so far that the virtualized list will load another window, dropping it there and then clicking save, the request payload will contain all promotions in between the old and new priority order. That feels to me like something’s wrong, why would we need to send all of this data to the server, when the server did just fine just knowing the 1-2 promotions before? It can cause a lot of data being transferred with that request, which in turn will lead to more time spent deserializing and ultimately a slower Save-request. It can also lead to quite terrific results, which I will explain in the next section:
(Bug) Dragging an item too far will make it unsavable
With the previous section in mind, those promotions in the prioritization view are quite big JSON objects when serialized! If you drag a promotion, say, 500 items up and try to save, you’ll get a JSON deserialization exception telling you that the thing to serialize is too big to serialize. Even 1000 serialized objects shouldn’t be too large to send, but as I said I think they are quite heavy, some colleagues of mine found that they had response sizes that were over 100mb for just one window load in the prioritization view. They think the cause of that is that they have over 1000 shipping methods, and all of them are serialized on each (shipping?) promotion. But I will skip that dilemma from this list, as they will sort it out on their side together with Epi! But this exception is definitely something to be looked at. How to reproduce:
- Drag a promotion from the bottom of a big virtualized list
- Drop it at the top
So far I’ve mostly stated things I think needs improvement, without providing that many suggestions in how to solve them. I don’t have suggestions for all things I pointed out above, but here goes.
Add alternative to drag and dropping for reordering promotions
Currently the only way of reordering a promotion is by dragging and dropping. While this is a very good way of doing it when there are a small amount of promotions or when it’s just a small movement, I think other options is needed for the other cases. It simply isn’t nice drag and dropping something far, it takes far too much time.
Perhaps a button that lets you input a priority order number that will move the promotion to that priority number? Or Move to top/Move to Bottom buttons.
Add a search/filter feature
To resolve the issue of finding a promotion, the same search/filter input feature as in the main screen of Marketing could be used here. Seeing as we can’t use the browser in page search.
Doesn’t need to be much more advanced than the mockup image above. It gets a bit more tricky what you would want to see as results. We don’t want to filter like we do on the main Marketing page, because if we found our promotion there would be nowhere to drag and drop it. Maybe we get a drop down list as a result of this search and when you select the one you want you are scrolled to it and it’s highlighted?
Don’t show irrelevant promotions unless requested
I think this is the biggest point and a “low hanging fruit”. Currently the list is always every promotion, regardless if they are active or not. Seeing as the promotion engine will remove all the promotions that are inactive from evaluation quite early in the process, before the prioritization/exclusions come into play, having this list filled with inactive is in 90% of the cases useless. However, there are still valid use cases for seeing inactive, such as just creating a new promotion and having it Scheduled and want to set it up before it go live. But just like the main Marketing view, we could use a facet system for “active”, “inactive” and “scheduled” and have it default to “active” only. In the most common use case, the performance will be much better seeing as probably only a minority of the promotions are active, and that minority will become a bigger minority over time, as more and more promotions expire and go inactive.
Keep the “add exclusion” popup rendered
One way of solving the 10 sec freeze when we click on the plus to add exclusions would be to not having to render that item every time, but simply render it once sometime after the page loaded and then simply clear all the checkboxes whenever it is closed or finished. Or simply optimize the render function for the promotion list in memory to the checkbox hierarchy in the DOM, maybe there are some cheap things one can do there.
Trust the frontend with the list
Whenever you reorder something and then save, there is no apparent need to refresh the entire list and it can be very expensive changing the DOM that much, as described. I’d say to just remove the refreshing of the list, trust the frontend that when you move something and then save, the list is identical as if you would reload the entire page again.
It’s always nice contributing/giving feedback in different ways. Blog post/forum threads are one, actual code is another way! It would be amazing if those who want to contribute can get more channels to do so!
*COUGH* PRs! *COUGH*