Skip to content

Commit

Permalink
feat: Install Appcues (#181)
Browse files Browse the repository at this point in the history
  • Loading branch information
mathcolo authored Dec 2, 2024
1 parent 8e69723 commit 0add271
Show file tree
Hide file tree
Showing 12 changed files with 85 additions and 6 deletions.
1 change: 1 addition & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Config

# Application config
config :orbit,
appcues_enabled?: false,
allow_test_data?: false,
ecto_repos: [Orbit.Repo],
generators: [timestamp_type: :utc_datetime],
Expand Down
9 changes: 7 additions & 2 deletions config/dev.secret.example.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
# Copy the template to dev.secret.exs, then uncomment and fill in any sections you need.
import Config

# Appcues
# config :orbit,
# appcues_enabled?: true,
# appcues_id: "SOME_ID"

# FullStory
# Only turn this on if you're testing sending data to FullStory, not for regular use.
config :orbit,
full_story_org_id: ""
# config :orbit,
# full_story_org_id: ""

# Sentry
# Only turn this on if you're testing sending events to Sentry, not for regular use.
Expand Down
1 change: 1 addition & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Config

config :orbit,
# loaded at compile time
appcues_enabled?: true,
release: System.get_env("RELEASE")

config :orbit, Orbit.Repo,
Expand Down
1 change: 1 addition & 0 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ if config_env() == :prod do

config :orbit,
allow_test_data?: System.get_env("ALLOW_TEST_DATA", "") == "yes",
appcues_id: System.get_env("APPCUES_ID"),
environment: System.get_env("ENVIRONMENT"),
full_story_org_id: System.get_env("FULLSTORY_ORG_ID")

Expand Down
1 change: 0 additions & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ config :orbit,
allow_test_data?: true,
environment: "test",
release: "test",
full_story_org_id: "RAWR",
force_https?: false

# Database config
Expand Down
2 changes: 2 additions & 0 deletions js/components/app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { reload } from "../browser";
import { paths } from "../paths";
import { AppcuesTrackPage } from "./appcues";
import { Header } from "./header";
import { Home } from "./home";
import { HelpMenu, Menu } from "./menus";
Expand Down Expand Up @@ -49,6 +50,7 @@ const router = createBrowserRouter([
errorElement: <ErrorBoundary />,
element: (
<>
<AppcuesTrackPage />
<Header />
<Outlet />
</>
Expand Down
45 changes: 45 additions & 0 deletions js/components/appcues.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { getMetaContent } from "../util/metadata";
import { useEffect } from "react";
import { useLocation } from "react-router-dom";

declare global {
const Appcues: {
identify(
userId: string,
properties?: Record<string, string | number | boolean>,
): void;
page(): void;
};
}

// React component, put inside react router
export const AppcuesTrackPage = (): null => {
const email = getMetaContent("userEmail") ?? "";
const appcuesUserId = getMetaContent("appcuesUserId");
const location = useLocation();
const appcuesInitialized: boolean = Object.prototype.hasOwnProperty.call(
globalThis,
"Appcues",
);
useEffect(() => {
if (appcuesInitialized && appcuesUserId !== null) {
try {
Appcues.identify(appcuesUserId, {
email,
});
} catch (error) {
console.warn(error);
}
}
}, [appcuesInitialized, email, appcuesUserId]);
useEffect(() => {
if (appcuesInitialized) {
try {
Appcues.page();
} catch (error) {
console.warn(error);
}
}
}, [location, appcuesInitialized]);
return null;
};
1 change: 1 addition & 0 deletions js/util/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type MetaDataKey =
| "appcuesUserId"
| "csrf-token"
| "fullStoryOrgId"
| "sentryDsn"
Expand Down
8 changes: 8 additions & 0 deletions lib/orbit_web/components/layouts/root.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,17 @@
<%= if @conn.assigns[:full_story_org_id] do %>
<meta name="fullStoryOrgId" content={@full_story_org_id} />
<% end %>
<%= if @conn.assigns[:appcues_uid] do %>
<meta name="appcuesUserId" content={@appcues_uid} />
<% end %>

<title><%= assigns[:page_title] || "Orbit" %></title>
<link rel="stylesheet" href={~p"/assets/app.css"} />

<%= if @conn.assigns[:appcues_enabled?] && @conn.assigns[:appcues_id] do %>
<script src={"//fast.appcues.com/#{@appcues_id}.js"}>
</script>
<% end %>
</head>
<body class="bg-white antialiased">
<%= @inner_content %>
Expand Down
16 changes: 14 additions & 2 deletions lib/orbit_web/controllers/react_app_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,21 @@ defmodule OrbitWeb.ReactAppController do
# so skip the default app layout.
render(conn, :react_app,
layout: false,
sentry_dsn: Application.get_env(:sentry, :dsn),
appcues_enabled?: Application.get_env(:orbit, :appcues_enabled?),
appcues_id: Application.get_env(:orbit, :appcues_id),
appcues_uid: appcues_uid(conn),
environment: Application.get_env(:orbit, :environment),
full_story_org_id: Application.get_env(:orbit, :full_story_org_id)
full_story_org_id: Application.get_env(:orbit, :full_story_org_id),
sentry_dsn: Application.get_env(:sentry, :dsn)
)
end

# Use half a sha256 of the email address as user id.
# This is not secure! But we're eliminating randomness
# while not being trivially overridable.
defp appcues_uid(conn) do
:crypto.hash(:sha3_256, conn.assigns[:email])
|> Base.encode16(case: :lower)
|> String.slice(0..32)
end
end
2 changes: 1 addition & 1 deletion lib/orbit_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule OrbitWeb.Router do

plug :put_secure_browser_headers, %{
"content-security-policy" =>
"default-src 'self'; connect-src 'self' *.sentry.io *.fullstory.com; script-src 'self' *.fullstory.com"
"default-src 'self'; connect-src 'self' *.sentry.io *.fullstory.com fast.appcues.com ws: api.appcues.net; script-src 'self' *.fullstory.com fast.appcues.com; style-src 'self' fast.appcues.com;"
}
end

Expand Down
4 changes: 4 additions & 0 deletions test/orbit_web/controllers/react_app_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ defmodule OrbitWeb.ReactAppControllerTest do
})

reassign_env(:sentry, :dsn, "https://example.com")
reassign_env(:orbit, :appcues_enabled?, true)
reassign_env(:orbit, :appcues_id, "APPKEWES")
reassign_env(:orbit, :full_story_org_id, "RAWR")
end

@tag :authenticated
Expand All @@ -27,6 +30,7 @@ defmodule OrbitWeb.ReactAppControllerTest do
assert response =~ "<meta name=\"release\" content=\"test\">"
assert response =~ "<meta name=\"userEmail\" content=\"user@example.com\">"
assert response =~ "<meta name=\"userName\" content=\"Art Read\">"
assert response =~ "<script src=\"//fast.appcues.com/APPKEWES.js\">"
assert response =~ "<meta name=\"sentryDsn\" content=\"https://example.com\">"
assert response =~ "<meta name=\"environment\" content=\"test\">"
assert response =~ "<meta name=\"fullStoryOrgId\" content=\"RAWR\">"
Expand Down

0 comments on commit 0add271

Please sign in to comment.