Skip to content

Commit

Permalink
Integrate PostHog session replay (#6176)
Browse files Browse the repository at this point in the history
* Add PostHog API Key to GitHub Action workflows

* Add `posthog-js`

* Setup PostHog for Rill Developer

* Setup PostHog for Rill Cloud

* Hand-off sessions from Rill Developer to Rill Cloud

* Don't send analytics in `e2e` and `dev` environments

* Fix lint

* Fix lint

* Properly include "Rill version" as a contextual property

* Review
  • Loading branch information
ericpgreen2 authored Dec 5, 2024
1 parent 8115ec5 commit 7226141
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 19 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/cli-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ jobs:
- name: Build and embed static UI
run: make cli.prepare
env:
RILL_UI_PUBLIC_PYLON_APP_ID: "26a0fdd2-3bd3-41e2-82bc-1b35a444729f"
RILL_UI_PUBLIC_INTAKE_USER: "data-modeler"
RILL_UI_PUBLIC_INTAKE_PASSWORD: ${{ secrets.RILL_INTAKE_PASSWORD }}
RILL_UI_PUBLIC_POSTHOG_API_KEY: "phc_4qnfUotXUuevk2zJN8ei8HgKXMynddEMI0wPI9XwzlS"
RILL_UI_PUBLIC_PYLON_APP_ID: "26a0fdd2-3bd3-41e2-82bc-1b35a444729f"

- name: Build Rill using Goreleaser
run: |-
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/rill-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ jobs:
npm run build -w web-admin
env:
RILL_UI_PUBLIC_RILL_ADMIN_URL: https://admin.${{ env.DOMAIN }}
RILL_UI_PUBLIC_POSTHOG_API_KEY: "phc_4qnfUotXUuevk2zJN8ei8HgKXMynddEMI0wPI9XwzlS"
RILL_UI_PUBLIC_PYLON_APP_ID: "26a0fdd2-3bd3-41e2-82bc-1b35a444729f"
RILL_UI_PUBLIC_VERSION: ${{ env.RILL_VERSION }}

Expand Down
43 changes: 42 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions web-admin/src/features/authentication/AvatarButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,27 @@
import { page } from "$app/stores";
import { redirectToLogout } from "@rilldata/web-admin/client/redirect-utils";
import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu";
import { createAdminServiceGetCurrentUser } from "../../client";
import {
initPylonChat,
type UserLike,
} from "@rilldata/web-common/features/help/initPylonChat";
import { posthogIdentify } from "@rilldata/web-common/lib/analytics/posthog";
import { createAdminServiceGetCurrentUser } from "../../client";
import ProjectAccessControls from "../projects/ProjectAccessControls.svelte";
import ViewAsUserPopover from "../view-as-user/ViewAsUserPopover.svelte";
const user = createAdminServiceGetCurrentUser();
let subMenuOpen = false;
$: if ($user.data?.user) initPylonChat($user.data.user as UserLike);
$: if ($user.data?.user) {
// Actions to take when the user is known
posthogIdentify($user.data.user.id, {
email: $user.data.user.email,
});
initPylonChat($user.data.user as UserLike);
}
$: ({ params } = $page);
function handlePylon() {
Expand Down
3 changes: 2 additions & 1 deletion web-admin/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
import BannerCenter from "@rilldata/web-common/components/banner/BannerCenter.svelte";
import NotificationCenter from "@rilldata/web-common/components/notifications/NotificationCenter.svelte";
import { featureFlags } from "@rilldata/web-common/features/feature-flags";
import { initPylonWidget } from "@rilldata/web-common/features/help/initPylonWidget";
import RillTheme from "@rilldata/web-common/layout/RillTheme.svelte";
import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient";
import { errorEventHandler } from "@rilldata/web-common/metrics/initMetrics";
import { QueryClientProvider } from "@tanstack/svelte-query";
import { onMount } from "svelte";
import ErrorBoundary from "../features/errors/ErrorBoundary.svelte";
import { createGlobalErrorCallback } from "../features/errors/error-utils";
import { initPylonWidget } from "@rilldata/web-common/features/help/initPylonWidget";
import TopNavigationBar from "../features/navigation/TopNavigationBar.svelte";
export let data;
Expand All @@ -39,6 +39,7 @@
featureFlags.set(true, "adminServer", "readOnly");
let removeJavascriptListeners: () => void;
initCloudMetrics()
.then(() => {
removeJavascriptListeners =
Expand Down
37 changes: 29 additions & 8 deletions web-admin/src/routes/+layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ensure the same single-page app behavior in development.
*/
export const ssr = false;

import { dev } from "$app/environment";
import {
adminServiceGetProject,
getAdminServiceGetProjectQueryKey,
Expand All @@ -13,15 +14,17 @@ import {
} from "@rilldata/web-admin/client";
import { redirectToLoginOrRequestAccess } from "@rilldata/web-admin/features/authentication/checkUserAccess";
import { fetchOrganizationPermissions } from "@rilldata/web-admin/features/organizations/selectors";
import { initPosthog } from "@rilldata/web-common/lib/analytics/posthog";
import { queryClient } from "@rilldata/web-common/lib/svelte-query/globalQueryClient.js";
import { error, type Page } from "@sveltejs/kit";
import { error, redirect, type Page } from "@sveltejs/kit";
import type { QueryFunction, QueryKey } from "@tanstack/svelte-query";
import {
adminServiceGetProjectWithBearerToken,
getAdminServiceGetProjectWithBearerTokenQueryKey,
} from "../features/public-urls/get-project-with-bearer-token.js";

export const load = async ({ params, url, route }) => {
// Route params
const { organization, project, token: routeToken } = params;
const pageState = {
url,
Expand All @@ -35,6 +38,30 @@ export const load = async ({ params, url, route }) => {
}
const token = searchParamToken ?? routeToken;

// Initialize analytics
const shouldSendAnalytics = !import.meta.env.VITE_PLAYWRIGHT_TEST && !dev;
if (shouldSendAnalytics) {
const rillVersion = import.meta.env.RILL_UI_VERSION;
const posthogSessionId = url.searchParams.get("ph_session_id") as
| string
| null;
initPosthog(rillVersion, posthogSessionId);
if (posthogSessionId) {
// Remove the PostHog sessionID from the url
url.searchParams.delete("ph_session_id");
throw redirect(307, url.toString());
}
}

// If no organization or project, return empty permissions
if (!organization || !project) {
return {
organizationPermissions: <V1OrganizationPermissions>{},
projectPermissions: <V1ProjectPermissions>{},
};
}

// Get organization permissions
let organizationPermissions: V1OrganizationPermissions = {};
if (organization && !token) {
try {
Expand All @@ -47,13 +74,7 @@ export const load = async ({ params, url, route }) => {
}
}

if (!organization || !project) {
return {
organizationPermissions,
projectPermissions: <V1ProjectPermissions>{},
};
}

// Get project permissions
let queryKey: QueryKey;
let queryFn: QueryFunction<
Awaited<ReturnType<typeof adminServiceGetProject>>
Expand Down
1 change: 1 addition & 0 deletions web-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"match-sorter": "^6.3.1",
"nearley": "^2.20.1",
"orval": "6.12.0",
"posthog-js": "^1.188.0",
"regular-table": "^0.5.9",
"storybook": "^7.0.18",
"svelte": "^4.2.19",
Expand Down
19 changes: 16 additions & 3 deletions web-common/src/features/project/ProjectDeployer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
localServiceGetCurrentUser,
} from "@rilldata/web-common/runtime-client/local-service";
import { derived, get, writable } from "svelte/store";
import { addPosthogSessionIdToUrl } from "../../lib/analytics/posthog";

export class ProjectDeployer {
public readonly metadata = createLocalServiceGetMetadata();
Expand Down Expand Up @@ -132,6 +133,8 @@ export class ProjectDeployer {
await waitUntil(() => !get(this.project).isLoading);

const projectResp = get(this.project).data as GetCurrentProjectResponse;

// Project already exists
if (projectResp.project) {
if (projectResp.project.githubUrl) {
// we do not support pushing to a project already connected to github
Expand All @@ -142,10 +145,14 @@ export class ProjectDeployer {
projectId: projectResp.project.id,
reupload: true,
});
window.open(resp.frontendUrl, "_self");
const projectUrl = resp.frontendUrl; // https://ui.rilldata.com/<org>/<project>
const projectUrlWithSessionId = addPosthogSessionIdToUrl(projectUrl);
window.open(projectUrlWithSessionId, "_self");
return;
}

// Project does not yet exist

if (!org && this.useOrg) {
org = this.useOrg;
}
Expand All @@ -160,12 +167,18 @@ export class ProjectDeployer {
checkNextOrg = inferredCheckNextOrg;
}

const frontendUrl = await this.tryDeployWithOrg(
const projectUrl = await this.tryDeployWithOrg(
org,
projectResp.localProjectName,
checkNextOrg,
);
if (frontendUrl) window.open(frontendUrl + "/-/invite", "_self");
if (projectUrl) {
// projectUrl: https://ui.rilldata.com/<org>/<project>
const projectInviteUrl = projectUrl + "/-/invite";
const projectInviteUrlWithSessionId =
addPosthogSessionIdToUrl(projectInviteUrl);
window.open(projectInviteUrlWithSessionId, "_self");
}
}

private async inferOrg(rillUserOrgs: string[]) {
Expand Down
43 changes: 43 additions & 0 deletions web-common/src/lib/analytics/posthog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import posthog, { type Properties } from "posthog-js";

const POSTHOG_API_KEY = import.meta.env.RILL_UI_PUBLIC_POSTHOG_API_KEY;

export function initPosthog(rillVersion: string, sessionId?: string | null) {
// No need to proceed if PostHog is already initialized
if (posthog.__loaded) return;

if (!POSTHOG_API_KEY) {
console.warn("PostHog API Key not found");
return;
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
posthog.init(POSTHOG_API_KEY, {
api_host: "https://us.i.posthog.com", // TODO: use a reverse proxy https://posthog.com/docs/advanced/proxy
session_recording: {
maskAllInputs: true,
maskTextSelector: "*",
recordHeaders: true,
recordBody: false,
},
autocapture: true,
enable_heatmaps: true,
bootstrap: {
sessionID: sessionId ?? undefined,
},
loaded: (posthog) => {
posthog.register_for_session({
"Rill version": rillVersion,
});
},
});
}

export function posthogIdentify(userID: string, userProperties?: Properties) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
posthog.identify(userID, userProperties);
}

export function addPosthogSessionIdToUrl(url: string) {
return url + "?ph_session_id=" + posthog.get_session_id();
}
12 changes: 12 additions & 0 deletions web-common/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// This file lets Typescript know about our custom environment variables
// See: https://vite.dev/guide/env-and-mode#intellisense-for-typescript

/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly RILL_UI_PUBLIC_POSTHOG_API_KEY: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
Loading

1 comment on commit 7226141

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.