diff --git a/webview-ui/src/components/common/DismissibleUpsell.tsx b/webview-ui/src/components/common/DismissibleUpsell.tsx
index 2eefb89972ef..6f0e1ad2dce4 100644
--- a/webview-ui/src/components/common/DismissibleUpsell.tsx
+++ b/webview-ui/src/components/common/DismissibleUpsell.tsx
@@ -1,6 +1,8 @@
import { memo, ReactNode, useEffect, useState, useRef } from "react"
import { vscode } from "@src/utils/vscode"
import { useAppTranslation } from "@src/i18n/TranslationContext"
+import { telemetryClient } from "@src/utils/TelemetryClient"
+import { TelemetryEventName } from "@roo-code/types"
interface DismissibleUpsellProps {
/** Required unique identifier for this upsell */
@@ -76,7 +78,12 @@ const DismissibleUpsell = memo(
}
}, [upsellId])
- const handleDismiss = async () => {
+ const handleDismiss = () => {
+ // Track telemetry for dismissal
+ telemetryClient.capture(TelemetryEventName.UPSELL_DISMISSED, {
+ upsellId: upsellId,
+ })
+
// First notify the extension to persist the dismissal
// This ensures the message is sent even if the component unmounts quickly
vscode.postMessage({
@@ -134,6 +141,13 @@ const DismissibleUpsell = memo(
{
+ // Track telemetry for click
+ if (onClick) {
+ telemetryClient.capture(TelemetryEventName.UPSELL_CLICKED, {
+ upsellId: upsellId,
+ })
+ }
+
// Call the onClick handler if provided
onClick?.()
// Also dismiss if dismissOnClick is true
diff --git a/webview-ui/src/components/common/__tests__/DismissibleUpsell.spec.tsx b/webview-ui/src/components/common/__tests__/DismissibleUpsell.spec.tsx
index 3af66dfdf189..69e4e94a4f5c 100644
--- a/webview-ui/src/components/common/__tests__/DismissibleUpsell.spec.tsx
+++ b/webview-ui/src/components/common/__tests__/DismissibleUpsell.spec.tsx
@@ -1,6 +1,7 @@
import { render, screen, fireEvent, waitFor, act } from "@testing-library/react"
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
import DismissibleUpsell from "../DismissibleUpsell"
+import { TelemetryEventName } from "@roo-code/types"
// Mock the vscode API
const mockPostMessage = vi.fn()
@@ -10,6 +11,14 @@ vi.mock("@src/utils/vscode", () => ({
},
}))
+// Mock telemetryClient
+const mockCapture = vi.fn()
+vi.mock("@src/utils/TelemetryClient", () => ({
+ telemetryClient: {
+ capture: (eventName: string, properties?: Record
) => mockCapture(eventName, properties),
+ },
+}))
+
// Mock the translation hook
vi.mock("@src/i18n/TranslationContext", () => ({
useAppTranslation: () => ({
@@ -26,6 +35,7 @@ vi.mock("@src/i18n/TranslationContext", () => ({
describe("DismissibleUpsell", () => {
beforeEach(() => {
mockPostMessage.mockClear()
+ mockCapture.mockClear()
vi.clearAllTimers()
})
@@ -72,7 +82,7 @@ describe("DismissibleUpsell", () => {
})
})
- it("hides the upsell when dismiss button is clicked", async () => {
+ it("hides the upsell when dismiss button is clicked and tracks telemetry", async () => {
const onDismiss = vi.fn()
const { container } = render(
@@ -92,6 +102,11 @@ describe("DismissibleUpsell", () => {
const dismissButton = screen.getByRole("button", { name: /dismiss/i })
fireEvent.click(dismissButton)
+ // Check that telemetry was tracked
+ expect(mockCapture).toHaveBeenCalledWith(TelemetryEventName.UPSELL_DISMISSED, {
+ upsellId: "test-upsell",
+ })
+
// Check that the dismiss message was sent BEFORE hiding
expect(mockPostMessage).toHaveBeenCalledWith({
type: "dismissUpsell",
@@ -351,7 +366,7 @@ describe("DismissibleUpsell", () => {
})
})
- it("calls onClick when the container is clicked", async () => {
+ it("calls onClick when the container is clicked and tracks telemetry", async () => {
const onClick = vi.fn()
render(
@@ -372,6 +387,11 @@ describe("DismissibleUpsell", () => {
fireEvent.click(container)
expect(onClick).toHaveBeenCalledTimes(1)
+
+ // Check that telemetry was tracked
+ expect(mockCapture).toHaveBeenCalledWith(TelemetryEventName.UPSELL_CLICKED, {
+ upsellId: "test-upsell",
+ })
})
it("does not call onClick when dismiss button is clicked", async () => {
@@ -470,7 +490,7 @@ describe("DismissibleUpsell", () => {
})
})
- it("dismisses when clicked if dismissOnClick is true", async () => {
+ it("dismisses when clicked if dismissOnClick is true and tracks both telemetry events", async () => {
const onClick = vi.fn()
const onDismiss = vi.fn()
const { container } = render(
@@ -493,6 +513,14 @@ describe("DismissibleUpsell", () => {
expect(onClick).toHaveBeenCalledTimes(1)
expect(onDismiss).toHaveBeenCalledTimes(1)
+ // Check that both telemetry events were tracked
+ expect(mockCapture).toHaveBeenCalledWith(TelemetryEventName.UPSELL_CLICKED, {
+ upsellId: "test-upsell",
+ })
+ expect(mockCapture).toHaveBeenCalledWith(TelemetryEventName.UPSELL_DISMISSED, {
+ upsellId: "test-upsell",
+ })
+
expect(mockPostMessage).toHaveBeenCalledWith({
type: "dismissUpsell",
upsellId: "test-upsell",
@@ -503,6 +531,46 @@ describe("DismissibleUpsell", () => {
})
})
+ it("dismisses on container click when dismissOnClick is true and no onClick is provided; tracks only dismissal", async () => {
+ const onDismiss = vi.fn()
+ const { container } = render(
+
+ Test content
+ ,
+ )
+
+ // Make component visible
+ makeUpsellVisible()
+
+ // Wait for component to be visible
+ await waitFor(() => {
+ expect(screen.getByText("Test content")).toBeInTheDocument()
+ })
+
+ // Click on the container (not the dismiss button)
+ const containerDiv = screen.getByText("Test content").parentElement as HTMLElement
+ fireEvent.click(containerDiv)
+
+ // onDismiss should be called
+ expect(onDismiss).toHaveBeenCalledTimes(1)
+
+ // Telemetry: only dismissal should be tracked
+ expect(mockCapture).toHaveBeenCalledWith(TelemetryEventName.UPSELL_DISMISSED, {
+ upsellId: "test-upsell",
+ })
+ expect(mockCapture).not.toHaveBeenCalledWith(TelemetryEventName.UPSELL_CLICKED, expect.anything())
+
+ // Dismiss message should be sent
+ expect(mockPostMessage).toHaveBeenCalledWith({
+ type: "dismissUpsell",
+ upsellId: "test-upsell",
+ })
+
+ // Component should be hidden
+ await waitFor(() => {
+ expect(container.firstChild).toBeNull()
+ })
+ })
it("does not dismiss when clicked if dismissOnClick is false", async () => {
const onClick = vi.fn()
const onDismiss = vi.fn()
diff --git a/webview-ui/src/components/settings/providers/Requesty.tsx b/webview-ui/src/components/settings/providers/Requesty.tsx
index 2f531a0eccca..82e4ef8e42ca 100644
--- a/webview-ui/src/components/settings/providers/Requesty.tsx
+++ b/webview-ui/src/components/settings/providers/Requesty.tsx
@@ -36,8 +36,6 @@ export const Requesty = ({
}: RequestyProps) => {
const { t } = useAppTranslation()
- const [didRefetch, setDidRefetch] = useState()
-
const [requestyEndpointSelected, setRequestyEndpointSelected] = useState(!!apiConfiguration.requestyBaseUrl)
// This ensures that the "Use custom URL" checkbox is hidden when the user deletes the URL.
@@ -131,18 +129,12 @@ export const Requesty = ({
onClick={() => {
vscode.postMessage({ type: "flushRouterModels", text: "requesty" })
refetchRouterModels()
- setDidRefetch(true)
}}>
{t("settings:providers.refreshModels.label")}
- {didRefetch && (
-
- {t("settings:providers.refreshModels.hint")}
-
- )}
Learn more.",
"longRunningTask": "This might take a while. Continue from anywhere with Cloud.",
diff --git a/webview-ui/src/i18n/locales/zh-CN/cloud.json b/webview-ui/src/i18n/locales/zh-CN/cloud.json
index babeef6d6720..0f89326a4cd0 100644
--- a/webview-ui/src/i18n/locales/zh-CN/cloud.json
+++ b/webview-ui/src/i18n/locales/zh-CN/cloud.json
@@ -24,7 +24,13 @@
"startOver": "重新开始",
"personalAccount": "个人账户",
"switchAccount": "切换 Roo Code Cloud 账户",
+ "createTeamAccount": "创建团队账户",
"cloudUrlPillLabel": "Roo Code Cloud URL",
+ "quota": {
+ "totalQuota": "总配额",
+ "usedQuota": "已使用",
+ "usageRate": "配额用量"
+ },
"upsell": {
"autoApprovePowerUser": "给 Roo 一些独立性?使用 Roo Code Cloud 从任何地方控制它。 了解更多。",
"longRunningTask": "这可能需要一段时间。使用 Cloud 从任何地方继续。",
diff --git a/webview-ui/src/i18n/locales/zh-TW/cloud.json b/webview-ui/src/i18n/locales/zh-TW/cloud.json
index 9ea24e23712c..fc1b79318a4d 100644
--- a/webview-ui/src/i18n/locales/zh-TW/cloud.json
+++ b/webview-ui/src/i18n/locales/zh-TW/cloud.json
@@ -24,7 +24,13 @@
"startOver": "重新開始",
"personalAccount": "個人帳戶",
"switchAccount": "切換 Roo Code Cloud 帳戶",
+ "createTeamAccount": "創建團隊帳戶",
"cloudUrlPillLabel": "Roo Code Cloud URL",
+ "quota": {
+ "totalQuota": "總配額",
+ "usedQuota": "已使用",
+ "usageRate": "配額用量"
+ },
"upsell": {
"autoApprovePowerUser": "給 Roo 一點獨立性?使用 Roo Code Cloud 隨時隨地控制它。了解更多。",
"longRunningTask": "這可能需要一些時間。使用雲端隨時隨地繼續。",