mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
chore(frontend): Add better PostHog tracking (#11645)
This commit is contained in:
parent
955f87561b
commit
ad75cd05d8
@ -8,6 +8,13 @@ vi.mock("#/hooks/use-auth-url", () => ({
|
||||
useAuthUrl: () => "https://gitlab.com/oauth/authorize",
|
||||
}));
|
||||
|
||||
// Mock the useTracking hook
|
||||
vi.mock("#/hooks/use-tracking", () => ({
|
||||
useTracking: () => ({
|
||||
trackLoginButtonClick: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("AuthModal", () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal("location", { href: "" });
|
||||
|
||||
@ -21,6 +21,7 @@ const mockUseConfig = vi.fn();
|
||||
const mockUseRepositoryMicroagents = vi.fn();
|
||||
const mockUseMicroagentManagementConversations = vi.fn();
|
||||
const mockUseSearchRepositories = vi.fn();
|
||||
const mockUseCreateConversationAndSubscribeMultiple = vi.fn();
|
||||
|
||||
vi.mock("#/hooks/use-user-providers", () => ({
|
||||
useUserProviders: () => mockUseUserProviders(),
|
||||
@ -47,6 +48,17 @@ vi.mock("#/hooks/query/use-search-repositories", () => ({
|
||||
useSearchRepositories: () => mockUseSearchRepositories(),
|
||||
}));
|
||||
|
||||
vi.mock("#/hooks/use-tracking", () => ({
|
||||
useTracking: () => ({
|
||||
trackEvent: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("#/hooks/use-create-conversation-and-subscribe-multiple", () => ({
|
||||
useCreateConversationAndSubscribeMultiple: () =>
|
||||
mockUseCreateConversationAndSubscribeMultiple(),
|
||||
}));
|
||||
|
||||
describe("MicroagentManagement", () => {
|
||||
const RouterStub = createRoutesStub([
|
||||
{
|
||||
@ -309,6 +321,16 @@ describe("MicroagentManagement", () => {
|
||||
isError: false,
|
||||
});
|
||||
|
||||
mockUseCreateConversationAndSubscribeMultiple.mockReturnValue({
|
||||
createConversationAndSubscribe: vi.fn(({ onSuccessCallback }) => {
|
||||
// Immediately call the success callback to close the modal
|
||||
if (onSuccessCallback) {
|
||||
onSuccessCallback();
|
||||
}
|
||||
}),
|
||||
isPending: false,
|
||||
});
|
||||
|
||||
// Mock the search repositories hook to return repositories with OpenHands suffixes
|
||||
const mockSearchResults =
|
||||
getRepositoriesWithOpenHandsSuffix(mockRepositories);
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import posthog from "posthog-js";
|
||||
import PRIcon from "#/icons/u-pr.svg?react";
|
||||
import { cn, getCreatePRPrompt } from "#/utils/utils";
|
||||
import { useUserProviders } from "#/hooks/use-user-providers";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { useTracking } from "#/hooks/use-tracking";
|
||||
|
||||
interface GitControlBarPrButtonProps {
|
||||
onSuggestionsClick: (value: string) => void;
|
||||
@ -20,6 +20,7 @@ export function GitControlBarPrButton({
|
||||
isConversationReady = true,
|
||||
}: GitControlBarPrButtonProps) {
|
||||
const { t } = useTranslation();
|
||||
const { trackCreatePrButtonClick } = useTracking();
|
||||
|
||||
const { providers } = useUserProviders();
|
||||
|
||||
@ -28,7 +29,7 @@ export function GitControlBarPrButton({
|
||||
providersAreSet && hasRepository && isConversationReady;
|
||||
|
||||
const handlePrClick = () => {
|
||||
posthog.capture("create_pr_button_clicked");
|
||||
trackCreatePrButtonClick();
|
||||
onSuggestionsClick(getCreatePRPrompt(currentGitProvider));
|
||||
};
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import posthog from "posthog-js";
|
||||
import ArrowDownIcon from "#/icons/u-arrow-down.svg?react";
|
||||
import { cn, getGitPullPrompt } from "#/utils/utils";
|
||||
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
|
||||
import { useUserProviders } from "#/hooks/use-user-providers";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { useTracking } from "#/hooks/use-tracking";
|
||||
|
||||
interface GitControlBarPullButtonProps {
|
||||
onSuggestionsClick: (value: string) => void;
|
||||
@ -16,6 +16,7 @@ export function GitControlBarPullButton({
|
||||
isConversationReady = true,
|
||||
}: GitControlBarPullButtonProps) {
|
||||
const { t } = useTranslation();
|
||||
const { trackPullButtonClick } = useTracking();
|
||||
|
||||
const { data: conversation } = useActiveConversation();
|
||||
const { providers } = useUserProviders();
|
||||
@ -26,7 +27,7 @@ export function GitControlBarPullButton({
|
||||
providersAreSet && hasRepository && isConversationReady;
|
||||
|
||||
const handlePullClick = () => {
|
||||
posthog.capture("pull_button_clicked");
|
||||
trackPullButtonClick();
|
||||
onSuggestionsClick(getGitPullPrompt());
|
||||
};
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import posthog from "posthog-js";
|
||||
import ArrowUpIcon from "#/icons/u-arrow-up.svg?react";
|
||||
import { cn, getGitPushPrompt } from "#/utils/utils";
|
||||
import { useUserProviders } from "#/hooks/use-user-providers";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { useTracking } from "#/hooks/use-tracking";
|
||||
|
||||
interface GitControlBarPushButtonProps {
|
||||
onSuggestionsClick: (value: string) => void;
|
||||
@ -20,6 +20,7 @@ export function GitControlBarPushButton({
|
||||
isConversationReady = true,
|
||||
}: GitControlBarPushButtonProps) {
|
||||
const { t } = useTranslation();
|
||||
const { trackPushButtonClick } = useTracking();
|
||||
|
||||
const { providers } = useUserProviders();
|
||||
|
||||
@ -28,7 +29,7 @@ export function GitControlBarPushButton({
|
||||
providersAreSet && hasRepository && isConversationReady;
|
||||
|
||||
const handlePushClick = () => {
|
||||
posthog.capture("push_button_clicked");
|
||||
trackPushButtonClick();
|
||||
onSuggestionsClick(getGitPushPrompt(currentGitProvider));
|
||||
};
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import BitbucketLogo from "#/assets/branding/bitbucket-logo.svg?react";
|
||||
import { useAuthUrl } from "#/hooks/use-auth-url";
|
||||
import { GetConfigResponse } from "#/api/option-service/option.types";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { useTracking } from "#/hooks/use-tracking";
|
||||
|
||||
interface AuthModalProps {
|
||||
githubAuthUrl: string | null;
|
||||
@ -26,6 +27,7 @@ export function AuthModal({
|
||||
providersConfigured,
|
||||
}: AuthModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const { trackLoginButtonClick } = useTracking();
|
||||
|
||||
const gitlabAuthUrl = useAuthUrl({
|
||||
appMode: appMode || null,
|
||||
@ -47,6 +49,7 @@ export function AuthModal({
|
||||
|
||||
const handleGitHubAuth = () => {
|
||||
if (githubAuthUrl) {
|
||||
trackLoginButtonClick({ provider: "github" });
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
window.location.href = githubAuthUrl;
|
||||
}
|
||||
@ -54,6 +57,7 @@ export function AuthModal({
|
||||
|
||||
const handleGitLabAuth = () => {
|
||||
if (gitlabAuthUrl) {
|
||||
trackLoginButtonClick({ provider: "gitlab" });
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
window.location.href = gitlabAuthUrl;
|
||||
}
|
||||
@ -61,6 +65,7 @@ export function AuthModal({
|
||||
|
||||
const handleBitbucketAuth = () => {
|
||||
if (bitbucketAuthUrl) {
|
||||
trackLoginButtonClick({ provider: "bitbucket" });
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
window.location.href = bitbucketAuthUrl;
|
||||
}
|
||||
@ -68,6 +73,7 @@ export function AuthModal({
|
||||
|
||||
const handleEnterpriseSsoAuth = () => {
|
||||
if (enterpriseSsoUrl) {
|
||||
trackLoginButtonClick({ provider: "enterprise_sso" });
|
||||
// Always start the OIDC flow, let the backend handle TOS check
|
||||
window.location.href = enterpriseSsoUrl;
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { SecretsService } from "#/api/secrets-service";
|
||||
import { Provider, ProviderToken } from "#/types/settings";
|
||||
import { useTracking } from "#/hooks/use-tracking";
|
||||
|
||||
export const useAddGitProviders = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { trackGitProviderConnected } = useTracking();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
@ -11,7 +13,18 @@ export const useAddGitProviders = () => {
|
||||
}: {
|
||||
providers: Record<Provider, ProviderToken>;
|
||||
}) => SecretsService.addGitProvider(providers),
|
||||
onSuccess: async () => {
|
||||
onSuccess: async (_, { providers }) => {
|
||||
// Track which providers were connected (filter out empty tokens)
|
||||
const connectedProviders = Object.entries(providers)
|
||||
.filter(([, value]) => value.token && value.token.trim() !== "")
|
||||
.map(([key]) => key);
|
||||
|
||||
if (connectedProviders.length > 0) {
|
||||
trackGitProviderConnected({
|
||||
providers: connectedProviders,
|
||||
});
|
||||
}
|
||||
|
||||
await queryClient.invalidateQueries({ queryKey: ["settings"] });
|
||||
},
|
||||
meta: {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import posthog from "posthog-js";
|
||||
import ConversationService from "#/api/conversation-service/conversation-service.api";
|
||||
import V1ConversationService from "#/api/conversation-service/v1-conversation-service.api";
|
||||
import { SuggestedTask } from "#/utils/types";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { CreateMicroagent, Conversation } from "#/api/open-hands.types";
|
||||
import { USE_V1_CONVERSATION_API } from "#/utils/feature-flags";
|
||||
import { useTracking } from "#/hooks/use-tracking";
|
||||
|
||||
interface CreateConversationVariables {
|
||||
query?: string;
|
||||
@ -31,6 +31,7 @@ interface CreateConversationResponse extends Partial<Conversation> {
|
||||
|
||||
export const useCreateConversation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { trackConversationCreated } = useTracking();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["create-conversation"],
|
||||
@ -86,12 +87,11 @@ export const useCreateConversation = () => {
|
||||
is_v1: false,
|
||||
};
|
||||
},
|
||||
onSuccess: async (_, { query, repository }) => {
|
||||
posthog.capture("initial_query_submitted", {
|
||||
entry_point: "task_form",
|
||||
query_character_length: query?.length,
|
||||
has_repository: !!repository,
|
||||
onSuccess: async (_, { repository }) => {
|
||||
trackConversationCreated({
|
||||
hasRepository: !!repository,
|
||||
});
|
||||
|
||||
queryClient.removeQueries({
|
||||
queryKey: ["user", "conversations"],
|
||||
});
|
||||
|
||||
77
frontend/src/hooks/use-tracking.ts
Normal file
77
frontend/src/hooks/use-tracking.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import posthog from "posthog-js";
|
||||
import { useConfig } from "./query/use-config";
|
||||
import { useSettings } from "./query/use-settings";
|
||||
import { Provider } from "#/types/settings";
|
||||
|
||||
/**
|
||||
* Hook that provides tracking functions with automatic data collection
|
||||
* from available hooks (config, settings, etc.)
|
||||
*/
|
||||
export const useTracking = () => {
|
||||
const { data: config } = useConfig();
|
||||
const { data: settings } = useSettings();
|
||||
|
||||
// Common properties included in all tracking events
|
||||
const commonProperties = {
|
||||
app_surface: config?.APP_MODE || "unknown",
|
||||
plan_tier: null,
|
||||
current_url: window.location.href,
|
||||
user_email: settings?.EMAIL || settings?.GIT_USER_EMAIL || null,
|
||||
};
|
||||
|
||||
const trackLoginButtonClick = ({ provider }: { provider: Provider }) => {
|
||||
posthog.capture("login_button_clicked", {
|
||||
provider,
|
||||
...commonProperties,
|
||||
});
|
||||
};
|
||||
|
||||
const trackConversationCreated = ({
|
||||
hasRepository,
|
||||
}: {
|
||||
hasRepository: boolean;
|
||||
}) => {
|
||||
posthog.capture("conversation_created", {
|
||||
has_repository: hasRepository,
|
||||
...commonProperties,
|
||||
});
|
||||
};
|
||||
|
||||
const trackPushButtonClick = () => {
|
||||
posthog.capture("push_button_clicked", {
|
||||
...commonProperties,
|
||||
});
|
||||
};
|
||||
|
||||
const trackPullButtonClick = () => {
|
||||
posthog.capture("pull_button_clicked", {
|
||||
...commonProperties,
|
||||
});
|
||||
};
|
||||
|
||||
const trackCreatePrButtonClick = () => {
|
||||
posthog.capture("create_pr_button_clicked", {
|
||||
...commonProperties,
|
||||
});
|
||||
};
|
||||
|
||||
const trackGitProviderConnected = ({
|
||||
providers,
|
||||
}: {
|
||||
providers: string[];
|
||||
}) => {
|
||||
posthog.capture("git_provider_connected", {
|
||||
providers,
|
||||
...commonProperties,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
trackLoginButtonClick,
|
||||
trackConversationCreated,
|
||||
trackPushButtonClick,
|
||||
trackPullButtonClick,
|
||||
trackCreatePrButtonClick,
|
||||
trackGitProviderConnected,
|
||||
};
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user