A Third Way, or: Updating a Legacy Rails App UI Without Scrapping the Whole Thing

Sarah Duve
The Casper Tech Blog: Z++
8 min readApr 23, 2019

--

Casper is known for a lot of things — beautiful design, ease of use, great customer service. But Bedpost, our internal app that same stellar customer service team spends hours on day in, day out? Not so much. In fact, if you read our design intern Elaine’s post, you know it’s been likened to an oyster — holding a wealth of information, but ugly and useless-looking!

In the beginning, there was this

How did it get this way?

The sad, bivalve-like state of Bedpost — which contains all of our shipping and fulfillment information — wasn’t the result of an extreme frontend skill deficiency, but rather symptomatic of something that befalls most internal tools: a lack of design resources and a difference of priority. Because internal tools frequently replace manual processes, anything at all is often a huge improvement over the current state of affairs. And in an effort to get these time-saving features rolled out to our internal customers as soon as possible, we often overlook such niceties as aesthetically pleasing and intuitive UIs. Since our users are often just an arm’s length or Slack message away, we can personally train them on the nine clicks it takes to get that one screen. And since the app is not public-facing, we can also be lax on permissions, and instead communicate out those things-you-can-technically-do-but-shouldn’t-ever, or at least, liberally sprinkle some sternly worded pop-ups here and there.

Of course, I’m glossing over the fact that more often than not, dev teams working on internal tools don’t even have the luxury of wireframes or mock ups to work from. Compound this approach over four years at a startup in hyper-growth state and you have…a lot of tech and design debt. Elaine began the process of figuring out how to pay down that debt. Now, nearly a year and a half later, we can tell you about the technical approach the engineering team ultimately took, and show you some of the results!

At the start of this redesign project, the Fulfillment pod found itself in a very interesting position. A team of mainly backend focused engineers, we had a set of beautiful new wireframes to implement on a four-year-old monolithic Rails app. An app that had never used anything other than plain Rails views with small amounts of jQuery as needed for its frontend. So how to go about this, in 2018, when confined by all the limitations of an internal tool but perhaps above all, time? While finally getting our users the Bedpost experience they deserved was of course important, it was only one of several epics the team tackled in the latter half of 2018, and rarely had more than one engineer allocated to it at a time.

Our Shop and Checkout pods had just recently undergone the process of decoupling their front and backends, adopting React SPAs with Rails API endpoints. But for a variety of reasons, this approach was never seriously considered for Bedpost. As mentioned, the team’s skill sets varied drastically compared to our frontend heavy pods, ranging from “I hate JavaScript and I don’t want to write any, ever” to, “sure, I wrote some React in production at my last job.” Given that pod and project assignments can change frequently and that no one engineer serves as the dedicated fulfillment “frontend person,” our approach had to more or less meet our devs where they were, avoiding huge learning curves and allowing the broadest contributions. Additionally, while having a store and checkout flow are evergreen needs for an e-commerce company, the purview of Bedpost has always been in more of a state of flux. While the future was certain enough that future ease of maintenance was a significant concern, ultimately form always takes a backseat to function in internal tools, and a project of the scope required for a SPA was deemed overkill.

Furthermore, since the redesign was addressing major usability issues and not a brand update or relaunch like so many public-facing redesigns are, we wanted to be able to roll out improvements to our coworkers and partners incrementally and as quickly as possible. This meant that while the project was in progress, old and newly redesigned pages would coexist side-by-side. With that in mind, knowing that we wanted to keep our frontend within the confines of Rails to make these small iterative improvements as straightforward and simple as possible, we considered another option on the complete other end of the spectrum — just…continuing to write jQuery. While on its face this initially seemed like the quickest and simplest solution, it just didn’t feel right, and for reasons beyond concern for who would be maintaining our jQuery soup in the future.

In recent years, it’s not just the JavaScript community that has decisively moved away from jQuery, but more and more, Rails itself. While the two communities have not always played nice, with 5.1, the framework embraced modern JavaScript practices by removing the jquery-rails gem as a default dependency and adding optional integration with the Webpacker gem, and as a result, out of the box support for React, Angular, and Vue.js. Github, a long time Rails stalwart, eventually followed suit by incrementally removing jQuery from its codebase (but notably, replacing it with vanilla JavaScript, not a framework). Even if JavaScript was not our principal language of choice, we didn’t want to use that as an excuse to remain entrenched in older technology and avoid embracing current best practices. And for some of us, the opportunity to learn a new framework like React was actually an incentive to work on a project that otherwise may have felt more like a chore. As mentioned, our frontend pods have already heavily invested in React, so it behooved us as engineers to build skills that had applications in the broader Casper codebase. React had the added benefit of being highly testable and would also bring some much-needed structure and organization to our JavaScript. It also raised the possibility of frontend devs on other pods contributing to Bedpost down the road. And while the lack of a stylistic overlap between the new Bedpost designs and the Casper storefront meant we were unlikely to utilize the component library built by our frontend colleagues, we still had the benefit of leveraging their expertise when necessary, allowing us to focus on what we do best as an operations tech pod.

We were seeking a third way between what seemed like two extremes, and lucky for us, around this same time RailsConf 2018 was taking place in Pittsburgh, conveniently with a “Modern JavaScript Rails” track of talks. Watching the livestream from New York, we drew particular inspiration from Michael Crismali’s talk “6 Degrees of JavaScript on Rails” which examined this very issue. As the name suggests, Michael’s talk outlined six different levels, or degrees of JavaScript use in Rails apps, ranging from none with Unobtrusive JavaScript, to the sixth degree, SPAs, that we had already eliminated from consideration. Since the pages we were redesigning varied greatly in the level of interactivity, an approach that combined the fourth and fifth degrees (“A Sprinkling of a Framework” and “Framework Pages”) seemed like the way to go.

After deciding that we did want to go ahead with using React within our existing Rails views, our next step was to choose a gem that would allow us to do this. Because rolling our own with just React and Webpacker would necessitate building out new API endpoints in order to pass props to the components, we knew we instead wanted to use a gem that had view helpers to handle that for us. Choosing between react-rails from the React team and ShakaCode’s React on Rails, we ultimately went with react-rails as it was the most lightweight solution with the fewest dependencies. While React on Rails is more powerful, with built-in support of both Redux and react-router, we were unlikely to need either so in the end, it was more than we needed.

So what does “degrees of React” look like in practice? We started with the most static pages and worked our way up in interactivity.

First up was the Shipment Show page.

With the exception of the “Update Shipment”, “Change Warehouse”, “Resolved Errors”, and “Previous Shipment Batches” tabs, this entire page is just HTML.

Compare this to the Warehouse Show page. The React Products and Variations tables were, in particular, a huge win for our users. Prior to the redesign, these tables opened on a separate page and took a very long time to load. Now our Ops team can quickly toggle between products and variations, and filter by Trading Partner, all without ever having to leave the warehouse page.

You’ll notice that we’ve still bypassed several opportunities to componentize. That’s because the mandate of this project was never “React all the things!”, but rather to just improve the Bedpost user experience — and it was important to not lose sight of that. And because we don’t anticipate much new frontend Bedpost development after this initial redesign, reusability was less of a concern than it is in our other frontend projects.

As mentioned earlier, being able to quickly roll out incremental improvements was important to us. To help accomplish this, we implemented a feature flag strategy using FeatureGuard. Rather than overwriting the existing designs, we put reworked views in separate “V2” namespaced files, determining which version to show based on the presence of the feature flag. We also conditionally pulled in either our old or new V2 styles in our application.html.haml file based on the @old_styles instance variable we set in the controller.

BedPost/app/controllers/shipments_controller.rb
BedPost/app/views/shipments/show.html.haml
BedPost/app/views/layouts/v2/application.html.haml

This approach allowed us to introduce the redesign on a page-by-page basis, but also enabled non-engineers to quickly rollback should an issue arise with the new page.

Having this tight feedback loop is not only key when responding to bugs, but is also a large part of what has made this project so rewarding.

In addition to this anecdotal ad-hoc feedback, we’re also conducting comprehensive CSAT (customer satisfaction) tracking.

And while we’re still awaiting the results following our most recent slate of changes, let’s just say we’re pretty confident this time around!

If this kind of problem-solving sounds rewarding to you, there’s good news! The Fulfillment pod is hiring. Check out our jobs page for this and several other openings across our Tech team at Casper.

Special thanks to Chandrika Achar and Winnie Ngo for their amazing work on this project as on display here, and to Michael Fisher for his support and guidance.

--

--