<?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>Forem: Brian Leonard</title>
    <description>The latest articles on Forem by Brian Leonard (@bleonard).</description>
    <link>https://forem.com/bleonard</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%2F634251%2F51ba1f5f-05fd-4f35-8021-1fc13eadc61f.jpeg</url>
      <title>Forem: Brian Leonard</title>
      <link>https://forem.com/bleonard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bleonard"/>
    <language>en</language>
    <item>
      <title>Data Makes Your Tools Smarter</title>
      <dc:creator>Brian Leonard</dc:creator>
      <pubDate>Wed, 26 May 2021 17:47:03 +0000</pubDate>
      <link>https://forem.com/grouparoo/data-makes-your-tools-smarter-638</link>
      <guid>https://forem.com/grouparoo/data-makes-your-tools-smarter-638</guid>
      <description>&lt;p&gt;When I was in charge of Product/Engineering at TaskRabbit, it was always challenging to prioritize integrations being requested by our Marketing, Sales, and Customer Success teams.&lt;/p&gt;

&lt;p&gt;First and foremost, most engineers just do not like working on this sort of thing. Often, this is the deciding factor in organizations. The work that gets done is the work that energizes people and no one was excited to brainstorm how we could most effectively sync data to Marketo.&lt;/p&gt;

&lt;p&gt;What they do want to work on is the core product. That is why they joined the company and critically, they know how to be successful there. The requirements for the Intercom integration are much fuzzier. It is hard to know what success looks like. The ambiguity prevents the momentum often necessary to get the work started, much less to the finish line.&lt;/p&gt;

&lt;p&gt;Of course, we also had our own goals. There were features to build, bugs to fix, metrics to move. When combined the the other factors, the integrations were at the bottom of the list despite the outsized opportunity to impact the business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customer Success
&lt;/h2&gt;

&lt;p&gt;One practice we had at TaskRabbit was periodically shadowing coworkers on other teams. It is enlightening and helps build the empathy that makes the business much stronger. If you have ever done this, particularly with a customer success representative, you will know what I am talking about when I say that it is very difficult to watch.&lt;/p&gt;

&lt;p&gt;The primary job is working tickets in a tool like Zendesk. As an engineer, the problems themselves made me sad because they were often bugs or fundamental misunderstandings on how the product worked. However, I had much more anxiety over the process of solving the ticket itself. Specifically, the data to help close those tickets is everywhere. Each ticket leads to multiple tabs just to get the right context and few more to solve the problem.&lt;/p&gt;

&lt;p&gt;When this data inefficiency is multiplied for all the customers and all the tickets and all the coworkers, it is a huge waste of time that negatively affects everyone: the customer, the coworker, the business.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The key concept of integrations is leverage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An data integration makes it possible to route the tickets better and surface key data right there in Zendesk. I've seen this result in 10x efficiency increase which as a huge impact on customer and coworker satisfaction, leading to a much stronger business.&lt;/p&gt;

&lt;p&gt;The leverage here is tremendous because of the volume, scale, and how manual things were to begin with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Marketing and Sales
&lt;/h2&gt;

&lt;p&gt;In Product organizations, we thing in product terms. What can we build that will attract more users? What feature will make them stay around longer? However, it often turns out that's the wrong solution space altogether.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;After the minimum viable product stage, the difference in many businesses is channels.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's agree that if nobody knows about the product, it doesn't matter what features you have. The solve for that isn't always a better landing page. It might be a Facebook ads integration to allow the Marketing team to create lookalike audiences.&lt;/p&gt;

&lt;p&gt;If someone has signed up or used the product, what's going to bring them back? Not your social proof on the funnel. And probably not the generic newsletter each month. The most effective tool here is personalized communication about their needs where they are in the journey, accomplished by syncing their data to tools like Mailchimp and leveraging that to be more relevant.&lt;/p&gt;

&lt;p&gt;B2B cases are all about timing. How can you reach the right people at the right time with the right message? It's not going to be a feature and it's not going to be blindly emailing all of the accounts. When Salesforce knows about recent activity in the product, you can use that data to trigger the right communication to better qualified leads.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Common Language
&lt;/h2&gt;

&lt;p&gt;Even with these strong business cases, integrations were always still hard to prioritize because of the unfamiliarity. Eventually, they had to happen for the company to scale. At that company and 100 more I've spoken with, it was always done later than it should have been.&lt;/p&gt;

&lt;p&gt;The cause of this delay is the lack of a common language between engineers and operational teams. When looking to solve this organizational problem, we set out to fill this gap with &lt;a href="https://www.grouparoo.com"&gt;Grouparoo&lt;/a&gt;. By defining patterns through an open source framework, both sides can engage on consistent footing. This approach makes data enablement easier and adds confidence, the core challenges for the engineers.&lt;/p&gt;

&lt;p&gt;I don't have any illusions about how exciting we can make data sync. It's unlikely product engineers will wake up in the morning thinking about Marketo's rate limits. My bar is pretty low.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I just want to make it possible for an engineer to respond with "Oh yeah, what do they need?" instead of "No, thanks." to an integration ask.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This response is a signal that integration work is on an equal footing with other features. At that point, everyone starts to realize the leverage and power of these integrations. They are low-hanging fruit that make a huge difference. Then, they get done, the tools get smarter, everyone has a better experience, and the whole business wins.&lt;/p&gt;

</description>
      <category>data</category>
      <category>marketing</category>
      <category>organization</category>
    </item>
    <item>
      <title>Debugging image dimensions with Next.js</title>
      <dc:creator>Brian Leonard</dc:creator>
      <pubDate>Wed, 19 May 2021 17:31:57 +0000</pubDate>
      <link>https://forem.com/grouparoo/debugging-image-dimensions-with-next-js-b8m</link>
      <guid>https://forem.com/grouparoo/debugging-image-dimensions-with-next-js-b8m</guid>
      <description>&lt;p&gt;I was writing a &lt;a href="https://www.grouparoo.com/blog/snowflake-source"&gt;blog post&lt;/a&gt;. In grand engineer tradition, I got distracted while blogging and spent a few hours writing tools to increase blogging efficiency.&lt;/p&gt;

&lt;p&gt;Specifically, I was having trouble knowing the correct &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; props to put on the screenshots I was making for the blog post. I would take the screenshot and then use image tools and even a spreadsheet to figure out the right ratio/dimensions for how I wanted it to show up in the UI.&lt;/p&gt;

&lt;p&gt;Now, if I get the dimensions wrong (or just want to see the correct ones), it will show it as a little red overlay in development. The banner uses the passed in dimensions to suggest changes that would match either the given height or width.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pa7RtTI---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zz6ht84pzej2y2jfcm8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pa7RtTI---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zz6ht84pzej2y2jfcm8l.png" alt="Logo with red banner suggesting size"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the correct sizing, it goes away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6aw8hWm3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gfnlee97yfj1oui172wu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6aw8hWm3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gfnlee97yfj1oui172wu.png" alt="Logo with no red banner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; has a fancy new &lt;a href="https://nextjs.org/docs/api-reference/next/image"&gt;image component&lt;/a&gt; that requires you pass in these &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; props. These are likely necessary to prevent the page shifting as images load, but if they do not match the ratio of the actual image, it will become distorted as above.&lt;/p&gt;

&lt;p&gt;When I was implementing the &lt;code&gt;next/image&lt;/code&gt; component the first time, we had to add these props for all of our website images. I made &lt;a href="https://github.com/grouparoo/www.grouparoo.com/blob/241a1c123f87299e754613bc01d33e1e96c3c8ae/scripts/make_image_csv.ts"&gt;this script&lt;/a&gt; that went through all of them and output the true widths, making this spreadsheet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Xf09QVDN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/poosx6ivuzzq0yq0792e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Xf09QVDN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/poosx6ivuzzq0yq0792e.png" alt="Spreadsheet of image dimensions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, I went through all our images and filled in the dimension (&lt;code&gt;maxWidth&lt;/code&gt; or &lt;code&gt;maxHeight&lt;/code&gt;) that I wanted to set. It then gave me the other dimension.&lt;/p&gt;

&lt;p&gt;This worked pretty well for the migration.&lt;/p&gt;

&lt;p&gt;However, the spreadsheet wasn't working for me as I was writing blog posts, though. I was figuring out the size from the MacOS "Get Info" menu item and adding them to the sheet manually. It took me out of the flow of writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;For example, the code for the logo above was the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/images/logos/nextjs.png"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The banner is accomplished with a new &lt;code&gt;DebugImage&lt;/code&gt; component that adds an an API request after the page loads. The server-side code seems to be needed to actually inspect the actual image on disk.&lt;/p&gt;

&lt;p&gt;The component checks with the API:&lt;br&gt;&lt;br&gt;
&lt;code&gt;GET /api/imgdim?w=600&amp;amp;h=300&amp;amp;url=%2Fimages%2Flogos%2Fnextjs.png&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"optimized"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"W x H: 600 x 539, 334 x 300"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dimensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;539&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;334&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.1136363636363635&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it gets fixed (by changing the &lt;code&gt;width&lt;/code&gt; to 334):&lt;br&gt;&lt;br&gt;
&lt;code&gt;GET /api/imgdim?w=334&amp;amp;h=300&amp;amp;url=%2Fimages%2Flogos%2Fnextjs.png&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"optimized"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Optimized"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dimensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;334&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;334&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.1136363636363635&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code in the &lt;code&gt;DebugImage&lt;/code&gt; component takes the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Doesn't do anything unless the &lt;code&gt;NODE_ENV&lt;/code&gt; is "development"&lt;/li&gt;
&lt;li&gt;Otherwise, wraps the component in a &lt;code&gt;div&lt;/code&gt; with no border, padding, etc&lt;/li&gt;
&lt;li&gt;If the image is not &lt;code&gt;optimized&lt;/code&gt; shows the message in the top left corner&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I guess now, I have some work to do...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n_Nh0myh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ko7yoftlrg4del4hgfh6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n_Nh0myh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ko7yoftlrg4del4hgfh6.png" alt="Image mistakes on homepage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maybe there's also a way to catch this in our test suite. There seems to be a few options there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use selenium and look for this banner, but that would be pretty slow&lt;/li&gt;
&lt;li&gt;Compile the site, but with a stubbed &lt;code&gt;Image&lt;/code&gt; component that records what's being used. Compare those dimensions to the ones on disk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anyway, I made my original blog post, will make future ones easier, and even got an extra one out of it!&lt;/p&gt;

&lt;p&gt;Here is the &lt;a href="https://github.com/grouparoo/www.grouparoo.com/pull/124"&gt;pull request&lt;/a&gt; with all the code.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
