<?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: Pontus Abrahamsson</title>
    <description>The latest articles on Forem by Pontus Abrahamsson (@pontusab).</description>
    <link>https://forem.com/pontusab</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%2F1429293%2F24885410-c601-4b20-809a-11e4a1ea79b6.png</url>
      <title>Forem: Pontus Abrahamsson</title>
      <link>https://forem.com/pontusab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/pontusab"/>
    <language>en</language>
    <item>
      <title>How we take data-driven decisions with LogSnag</title>
      <dc:creator>Pontus Abrahamsson</dc:creator>
      <pubDate>Tue, 16 Apr 2024 15:50:40 +0000</pubDate>
      <link>https://forem.com/pontusab/how-we-take-data-driven-decisions-with-logsnag-2nco</link>
      <guid>https://forem.com/pontusab/how-we-take-data-driven-decisions-with-logsnag-2nco</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fln61zi7jqytcnr5es51g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fln61zi7jqytcnr5es51g.png" alt="Image description" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's important for every new product to take the right decisions as often you can, and thanks for our public roadmap, votes on feature request and building in public we have a deep connection with our users, but on top of that we also want data to back our decisions. And thats why we have implemented LogSnag to track a lot of events so we can take data-driven decisions too.&lt;/p&gt;

&lt;p&gt;There are planty of ways how you can implement analytics, in this blog post we will share how we solved it in &lt;a href="https://midday.ai"&gt;Midday&lt;/a&gt; using NextJS and server-actions.&lt;/p&gt;

&lt;p&gt;Because we have a monorepo we started with creating a new package called &lt;code&gt;@midday/events&lt;/code&gt; where we install &lt;code&gt;@logsnag/next&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The package includes all the events we want to track in Midday for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    SignedIn: {
        name: "User Signed In",
        icon: "🌝",
        channel: "login",
    },
        SignOut: {
        name: "User Signed Out",
        icon: "🌝",
        channel: "login",
    },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And based of these realtime events we can make clear graphs like Line Chart, Bar Chart and Funnel Charts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fymsige3uwbfhpwt7koix.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fymsige3uwbfhpwt7koix.png" alt="Image description" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How we implemented LogSnag
&lt;/h3&gt;

&lt;p&gt;When you sign in to &lt;a href="https://midday.ai"&gt;Midday&lt;/a&gt; we first ask you about tracking, we want you to keep your privacy. &lt;/p&gt;

&lt;p&gt;This is done by showing a &lt;code&gt;Toast&lt;/code&gt; component with the option to Accept or Decline, we save this decision in a cookie so we now if we should add a identifier for the events or not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapdo4j0q64piasq2u79a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapdo4j0q64piasq2u79a.png" alt="Image description" width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We run a server action called &lt;code&gt;tracking-consent-action.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use server";

import { Cookies } from "@/utils/constants";
import { addYears } from "date-fns";
import { cookies } from "next/headers";
import { action } from "./safe-action";
import { trackingConsentSchema } from "./schema";

export const trackingConsentAction = action(
  trackingConsentSchema,
  async (value) =&amp;gt; {
    cookies().set({
      name: Cookies.TrackingConsent,
      value: value ? "1" : "0",
      expires: addYears(new Date(), 1),
    });

    return value;
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then wrap the &lt;code&gt;track&lt;/code&gt; method from LogSnag to enable or disabled the &lt;code&gt;user_id&lt;/code&gt; to the event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const setupLogSnag = async (options?: Props) =&amp;gt; {
  const { userId, fullName } = options ?? {};
  const consent = cookies().get(Cookies.TrackingConsent)?.value === "0";

  const logsnag = new LogSnag({
    token: process.env.LOGSNAG_PRIVATE_TOKEN!,
    project: process.env.NEXT_PUBLIC_LOGSNAG_PROJECT!,
    disableTracking: Boolean(process.env.NEXT_PUBLIC_LOGSNAG_DISABLED!),
  });

  if (consent &amp;amp;&amp;amp; userId &amp;amp;&amp;amp; fullName) {
    await logsnag.identify({
      user_id: userId,
      properties: {
        name: fullName,
      },
    });
  }

  return {
    ...logsnag,
    track: (options: TrackOptions) =&amp;gt;
      logsnag.track({
        ...options,
        user_id: consent ? userId : undefined,
      }),
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the &lt;code&gt;setupLogSnag&lt;/code&gt; function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const exportTransactionsAction = action(
  exportTransactionsSchema,
  async (transactionIds) =&amp;gt; {
    const user = await getUser();

    const event = await client.sendEvent({
      name: Events.TRANSACTIONS_EXPORT,
      payload: {
        transactionIds,
        teamId: user.data.team_id,
        locale: user.data.locale,
      },
    });

    const logsnag = await setupLogSnag({
      userId: user.data.id,
      fullName: user.data.full_name,
    });

    logsnag.track({
      event: LogEvents.ExportTransactions.name,
      icon: LogEvents.ExportTransactions.icon,
      channel: LogEvents.ExportTransactions.channel,
    });

    return event;
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have a lot of events and charts pushing us to take right decisions, you can find the source code for this in our repository &lt;a href="https://git.new/midday"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>analytics</category>
      <category>tracking</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
