mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-10 22:41:14 +00:00
feat(ROO-538): add send error to support button for authenticated users
This commit is contained in:
parent
4ee494b08c
commit
4fc418c130
5 changed files with 186 additions and 6 deletions
|
|
@ -507,6 +507,7 @@ export interface WebviewMessage {
|
|||
| "openDebugApiHistory"
|
||||
| "openDebugUiHistory"
|
||||
| "downloadErrorDiagnostics"
|
||||
| "sendErrorToSupport"
|
||||
| "requestClaudeCodeRateLimits"
|
||||
| "refreshCustomTools"
|
||||
| "requestModes"
|
||||
|
|
|
|||
|
|
@ -3310,6 +3310,78 @@ export const webviewMessageHandler = async (
|
|||
break
|
||||
}
|
||||
|
||||
case "sendErrorToSupport": {
|
||||
const currentTask = provider.getCurrentTask()
|
||||
if (!currentTask) {
|
||||
vscode.window.showErrorMessage("No active task to send error details")
|
||||
break
|
||||
}
|
||||
|
||||
try {
|
||||
// Get the cloud API URL from the message values
|
||||
const cloudApiUrl =
|
||||
message.values?.cloudApiUrl || process.env.ROO_CODE_CLOUD_URL || "https://app.roocode.com"
|
||||
const contactEndpoint = `${cloudApiUrl}/api/contact/issue`
|
||||
|
||||
// Prepare the error details JSON
|
||||
const errorData = {
|
||||
timestamp: message.values?.timestamp || new Date().toISOString(),
|
||||
version: message.values?.version || "",
|
||||
provider: message.values?.provider || "",
|
||||
model: message.values?.model || "",
|
||||
details: message.values?.details || "",
|
||||
taskId: currentTask.taskId,
|
||||
}
|
||||
|
||||
// Create a FormData-like structure
|
||||
const errorJson = JSON.stringify(errorData, null, 2)
|
||||
const boundary = `----RooCodeBoundary${Date.now()}`
|
||||
|
||||
const body = [
|
||||
`--${boundary}`,
|
||||
'Content-Disposition: form-data; name="file"; filename="error-details.json"',
|
||||
"Content-Type: application/json",
|
||||
"",
|
||||
errorJson,
|
||||
`--${boundary}--`,
|
||||
].join("\r\n")
|
||||
|
||||
// Get the session token from CloudService
|
||||
const sessionToken = CloudService.hasInstance()
|
||||
? CloudService.instance.authService?.getSessionToken()
|
||||
: undefined
|
||||
|
||||
if (!sessionToken) {
|
||||
vscode.window.showErrorMessage("You must be logged in to send error details to support")
|
||||
break
|
||||
}
|
||||
|
||||
// Make the authenticated POST request
|
||||
const response = await fetch(contactEndpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${sessionToken}`,
|
||||
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
||||
},
|
||||
body: body,
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
vscode.window.showInformationMessage("Error details sent to Roo Code support successfully")
|
||||
} else {
|
||||
const errorText = await response.text()
|
||||
provider.log(`Failed to send error details to support: ${response.status} ${errorText}`)
|
||||
vscode.window.showErrorMessage("Failed to send error details to support. Please try again later.")
|
||||
}
|
||||
} catch (error) {
|
||||
provider.log(
|
||||
`Error sending error details to support: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
vscode.window.showErrorMessage("Failed to send error details to support. Please try again later.")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
// console.log(`Unhandled message type: ${message.type}`)
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useCallback, memo, useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import { BookOpenText, MessageCircleWarning, Copy, Check, Microscope, Info } from "lucide-react"
|
||||
import { BookOpenText, MessageCircleWarning, Copy, Check, Microscope, Info, Send } from "lucide-react"
|
||||
|
||||
import { useCopyToClipboard } from "@src/utils/clipboard"
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
|
|
@ -94,8 +94,9 @@ export const ErrorRow = memo(
|
|||
const [showCopySuccess, setShowCopySuccess] = useState(false)
|
||||
const [isDetailsDialogOpen, setIsDetailsDialogOpen] = useState(false)
|
||||
const [showDetailsCopySuccess, setShowDetailsCopySuccess] = useState(false)
|
||||
const [showSendSuccess, setShowSendSuccess] = useState(false)
|
||||
const { copyWithFeedback } = useCopyToClipboard()
|
||||
const { version, apiConfiguration } = useExtensionState()
|
||||
const { version, apiConfiguration, cloudIsAuthenticated, cloudApiUrl } = useExtensionState()
|
||||
const { provider, id: modelId } = useSelectedModel(apiConfiguration)
|
||||
|
||||
const usesProxy = PROVIDERS.find((p) => p.value === provider)?.proxy ?? false
|
||||
|
|
@ -133,6 +134,28 @@ export const ErrorRow = memo(
|
|||
[version, provider, modelId, errorDetails],
|
||||
)
|
||||
|
||||
const handleSendToSupport = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
vscode.postMessage({
|
||||
type: "sendErrorToSupport",
|
||||
values: {
|
||||
timestamp: new Date().toISOString(),
|
||||
version,
|
||||
provider,
|
||||
model: modelId,
|
||||
details: errorDetails || "",
|
||||
cloudApiUrl,
|
||||
},
|
||||
})
|
||||
setShowSendSuccess(true)
|
||||
setTimeout(() => {
|
||||
setShowSendSuccess(false)
|
||||
}, 2000)
|
||||
},
|
||||
[version, provider, modelId, errorDetails, cloudApiUrl],
|
||||
)
|
||||
|
||||
// Default titles for different error types
|
||||
const getDefaultTitle = () => {
|
||||
if (title) return title
|
||||
|
|
@ -307,6 +330,21 @@ export const ErrorRow = memo(
|
|||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
{cloudIsAuthenticated && (
|
||||
<Button variant="secondary" className="w-full" onClick={handleSendToSupport}>
|
||||
{showSendSuccess ? (
|
||||
<>
|
||||
<Check className="size-3" />
|
||||
{t("chat:errorDetails.sent")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="size-3" />
|
||||
{t("chat:errorDetails.sendToSupport")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="secondary" className="w-full" onClick={handleCopyDetails}>
|
||||
{showDetailsCopySuccess ? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -13,11 +13,15 @@ vi.mock("@/utils/vscode", () => ({
|
|||
}))
|
||||
|
||||
// Mock ExtensionState context
|
||||
const mockExtensionState = {
|
||||
version: "1.0.0",
|
||||
apiConfiguration: {},
|
||||
cloudIsAuthenticated: false,
|
||||
cloudApiUrl: "https://app.roocode.com",
|
||||
}
|
||||
|
||||
vi.mock("@/context/ExtensionStateContext", () => ({
|
||||
useExtensionState: () => ({
|
||||
version: "1.0.0",
|
||||
apiConfiguration: {},
|
||||
}),
|
||||
useExtensionState: () => mockExtensionState,
|
||||
}))
|
||||
|
||||
// Mock selected model hook
|
||||
|
|
@ -38,6 +42,8 @@ vi.mock("react-i18next", () => ({
|
|||
"chat:errorDetails.copyToClipboard": "Copy to Clipboard",
|
||||
"chat:errorDetails.copied": "Copied!",
|
||||
"chat:errorDetails.diagnostics": "Get detailed error info",
|
||||
"chat:errorDetails.sendToSupport": "Send details to Roo Code support",
|
||||
"chat:errorDetails.sent": "Sent!",
|
||||
}
|
||||
return map[key] ?? key
|
||||
},
|
||||
|
|
@ -79,4 +85,65 @@ describe("ErrorRow diagnostics download", () => {
|
|||
// Timestamp is generated at runtime, but should be a string
|
||||
expect(typeof payload.values.timestamp).toBe("string")
|
||||
})
|
||||
|
||||
it("does not show send to support button when user is not authenticated", () => {
|
||||
mockExtensionState.cloudIsAuthenticated = false
|
||||
|
||||
render(<ErrorRow type="error" message="Something went wrong" errorDetails="Detailed error body" />)
|
||||
|
||||
// Open the Error Details dialog via the info button
|
||||
const infoButton = screen.getByRole("button", { name: "Error Details" })
|
||||
fireEvent.click(infoButton)
|
||||
|
||||
// The send to support button should not be present
|
||||
const sendButton = screen.queryByRole("button", { name: "Send details to Roo Code support" })
|
||||
expect(sendButton).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("shows send to support button when user is authenticated", () => {
|
||||
mockExtensionState.cloudIsAuthenticated = true
|
||||
|
||||
render(<ErrorRow type="error" message="Something went wrong" errorDetails="Detailed error body" />)
|
||||
|
||||
// Open the Error Details dialog via the info button
|
||||
const infoButton = screen.getByRole("button", { name: "Error Details" })
|
||||
fireEvent.click(infoButton)
|
||||
|
||||
// The send to support button should be present
|
||||
const sendButton = screen.getByRole("button", { name: "Send details to Roo Code support" })
|
||||
expect(sendButton).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it("sends sendErrorToSupport message when authenticated user clicks send to support button", () => {
|
||||
mockExtensionState.cloudIsAuthenticated = true
|
||||
const mockPostMessage = vi.mocked(vscode.postMessage)
|
||||
|
||||
render(<ErrorRow type="error" message="Something went wrong" errorDetails="Detailed error body" />)
|
||||
|
||||
// Open the Error Details dialog via the info button
|
||||
const infoButton = screen.getByRole("button", { name: "Error Details" })
|
||||
fireEvent.click(infoButton)
|
||||
|
||||
// Click the send to support button
|
||||
const sendButton = screen.getByRole("button", { name: "Send details to Roo Code support" })
|
||||
fireEvent.click(sendButton)
|
||||
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
const call = mockPostMessage.mock.calls.find(([arg]) => arg.type === "sendErrorToSupport")
|
||||
expect(call).toBeTruthy()
|
||||
if (!call) return
|
||||
|
||||
const payload = call[0] as { type: string; values?: any }
|
||||
expect(payload.values).toBeTruthy()
|
||||
if (!payload.values) return
|
||||
|
||||
expect(payload.values).toMatchObject({
|
||||
version: "1.0.0",
|
||||
provider: "test-provider",
|
||||
model: "test-model",
|
||||
cloudApiUrl: "https://app.roocode.com",
|
||||
})
|
||||
// Timestamp is generated at runtime, but should be a string
|
||||
expect(typeof payload.values.timestamp).toBe("string")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -304,6 +304,8 @@
|
|||
"copyToClipboard": "Copy basic error info",
|
||||
"copied": "Copied!",
|
||||
"diagnostics": "Get detailed error info",
|
||||
"sendToSupport": "Send details to Roo Code support",
|
||||
"sent": "Sent!",
|
||||
"proxyProvider": "You seem to be using a proxy-based provider. Make sure to check its logs and to ensure it's not rewriting Roo's requests."
|
||||
},
|
||||
"diffError": {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue