Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions apps/docs/anonymous-analytics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ When anonymous telemetry is enabled, your deployment sends:

- **Usage events** — page views (as route patterns like `/task/[taskId]`,
never actual URLs or IDs) and product events such as tasks being created
or settling as completed, failed, or canceled, with non-identifying facts
like the harness, model, source surface, and sandbox provider used.
or settling as completed, failed, or canceled, plus setup progress such as
reaching authenticated setup and configuring or connecting communications,
source-control, inference, and sandbox providers. Setup events include only
the provider type and, where Roomote can determine it from deployment state,
whether it was configured before the wizard. Other events include
non-identifying facts like the harness, model, source surface, and sandbox
provider used.
- **A daily instance report** — aggregate deployment metadata and usage:
setup timestamps; counts of users, environments, and connected repositories;
task, model, token, and cost totals for the past day; pull-request statistics
Expand Down
14 changes: 10 additions & 4 deletions apps/web/src/app/(onboarding)/setup/SetupBootstrapFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { AnimatePresence, motion, type Variants } from 'motion/react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';

import { useRedirectToSignIn } from '@/hooks/useSignInRedirect';
Expand Down Expand Up @@ -94,6 +94,9 @@ export function SetupBootstrapFlow() {
},
),
);
const trackWelcomeSeen = useMutation(
trpc.setupBootstrap.trackWelcomeSeen.mutationOptions(),
);
const bootstrapAuthProvider = getBootstrapAuthProvider(
bootstrapStatus?.authSetup,
pendingSetupAuthProvider,
Expand Down Expand Up @@ -225,11 +228,14 @@ export function SetupBootstrapFlow() {
>
{bootstrapStep === 'welcome' && (
<StepWelcome
onContinue={() =>
onContinue={() => {
trackWelcomeSeen.mutate(
setupToken ? { setupToken } : undefined,
);
setBootstrapStepWithTransition(
getBootstrapStepAfterWelcome(bootstrapStatus.authSetup),
)
}
);
}}
/>
)}
{bootstrapStep === 'email-account' && (
Expand Down
12 changes: 11 additions & 1 deletion apps/web/src/app/(onboarding)/setup/SetupSignedInFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ export function SetupSignedInFlow() {
useState<ComputeProvider | null>(null);
const [pendingModelProvider, setPendingModelProvider] =
useState<SetupModelProviderId | null>(null);
const trackWelcomeSeen = useMutation(
trpc.setupNew.trackWelcomeSeen.mutationOptions(),
);
const {
data: setupStatus,
isLoading: isSetupStatusLoading,
Expand Down Expand Up @@ -291,7 +294,14 @@ export function SetupSignedInFlow() {
animate="center"
exit="exit"
>
{step === 'welcome' && <StepWelcome onContinue={goToNextStep} />}
{step === 'welcome' && (
<StepWelcome
onContinue={() => {
trackWelcomeSeen.mutate();
goToNextStep();
}}
/>
)}
{step === 'auth-provider' && (
<StepAuthProvider
additionalProviders={['telegram', 'discord']}
Expand Down

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

Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
'use client';

import { useEffect, useRef } from 'react';
import { useMutation } from '@tanstack/react-query';
import type { SetupAuthStatus } from '@roomote/types';
import { toast } from 'sonner';

import { useConnectSlack } from '@/hooks/slack';
import { useTeamsIntegrationStatus } from '@/hooks/teams';
import { useTRPC } from '@/trpc/client';
import { TaskStatusIndicator } from '@/components/sandbox';
import {
ArrowRight,
Expand Down Expand Up @@ -41,6 +44,7 @@ export function StepCommunicationConnect({
onBack?: () => void;
returnPath?: string;
}) {
const trpc = useTRPC();
const provider = getCommunicationProvider(authSetup);
const connectSlack = useConnectSlack(returnPath, {
onSuccess: (url) => {
Expand All @@ -49,6 +53,41 @@ export function StepCommunicationConnect({
onError: () => toast.error('Failed to connect Slack. Please try again.'),
});
const teamsIntegrationStatus = useTeamsIntegrationStatus();
const configuredTrackedRef = useRef(false);
const authedTrackedRef = useRef(false);
const trackCommsState = useMutation(
trpc.setupNew.trackCommsState.mutationOptions(),
);
const teamsStatus = teamsIntegrationStatus.data;
const teamsConfigured =
teamsStatus?.botConfigured === true && teamsStatus.microsoftAuthConfigured;
const primaryConversationReady = Boolean(
teamsStatus?.primaryConversationReady,
);
useEffect(() => {
if (
provider === 'microsoft' &&
teamsConfigured &&
!configuredTrackedRef.current
) {
configuredTrackedRef.current = true;
trackCommsState.mutate({
provider: 'microsoft',
});
}
}, [provider, teamsConfigured, trackCommsState]);
useEffect(() => {
if (
provider === 'microsoft' &&
primaryConversationReady &&
!authedTrackedRef.current
) {
authedTrackedRef.current = true;
trackCommsState.mutate({
provider: 'microsoft',
});
}
}, [primaryConversationReady, provider, trackCommsState]);
const skipLink = (
<button
type="button"
Expand All @@ -59,16 +98,9 @@ export function StepCommunicationConnect({
</button>
);
if (provider === 'microsoft') {
const teamsStatus = teamsIntegrationStatus.data;
const openInTeamsUrl = teamsStatus?.openInTeamsUrl ?? null;
const teamsBotName = teamsStatus?.botName?.trim() || 'Roomote';
const teamsReady =
teamsStatus?.botConfigured === true &&
teamsStatus.microsoftAuthConfigured &&
openInTeamsUrl !== null;
const primaryConversationReady = Boolean(
teamsStatus?.primaryConversationReady,
);
const teamsReady = teamsConfigured && openInTeamsUrl !== null;

return (
<div className="relative w-full max-w-xl space-y-6 py-2 md:py-0">
Expand Down

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

33 changes: 31 additions & 2 deletions apps/web/src/app/(onboarding)/setup/StepDiscordSetup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';

Expand Down Expand Up @@ -37,6 +37,11 @@ export function StepDiscordSetup({
const [clearedSavedValues, setClearedSavedValues] = useState<
Record<string, boolean>
>({});
const configuredTrackedRef = useRef(false);
const authedTrackedRef = useRef(false);
const trackCommsState = useMutation(
trpc.setupNew.trackCommsState.mutationOptions(),
);
const provider = useMemo(
() => status.data?.providers.find((item) => item.id === 'discord') ?? null,
[status.data?.providers],
Expand All @@ -45,6 +50,11 @@ export function StepDiscordSetup({
const save = useMutation(
trpc.comms.saveAuthConfig.mutationOptions({
onSuccess: async (result) => {
setCredentialsSaved(true);
configuredTrackedRef.current = true;
trackCommsState.mutate({
provider: 'discord',
});
await Promise.all([
queryClient.invalidateQueries({
queryKey: trpc.comms.status.queryKey(),
Expand All @@ -58,12 +68,31 @@ export function StepDiscordSetup({
`Discord was saved, but Roomote could not finish connecting: ${result.discord.error ?? 'unknown error'}`,
);
}
setCredentialsSaved(true);
},
onError: (error) => toast.error(error.message),
}),
);
const isConfigured = credentialsSaved || provider?.setupSatisfied === true;
useEffect(() => {
if (
provider?.setupSatisfied === true &&
!credentialsSaved &&
!configuredTrackedRef.current
) {
configuredTrackedRef.current = true;
trackCommsState.mutate({
provider: 'discord',
});
}
}, [credentialsSaved, provider?.setupSatisfied, trackCommsState]);
useEffect(() => {
if (discordAccount.data?.mapping && !authedTrackedRef.current) {
authedTrackedRef.current = true;
trackCommsState.mutate({
provider: 'discord',
});
}
}, [discordAccount.data?.mapping, trackCommsState]);
const isActionDisabled =
save.isPending ||
status.isLoading ||
Expand Down

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

Loading
Loading