Skip to content

Commit e339b72

Browse files
author
Eric Wheeler
committed
ui: add upgrade handler for task history migration
Implement a structured upgrade system that manages the task history migration process: - Create a dedicated upgrade UI that blocks normal app usage until migration completes - Separate migration check from migration execution for better control flow - Add progress logging during migration to provide user feedback - Remove automatic migration during extension activation - Add new message types for upgrade status and completion This change improves the user experience during task history migration by providing visual feedback and ensuring the app is in a consistent state before allowing normal usage. The upgrade system is designed to be extensible for future structural upgrades beyond task history migration. Signed-off-by: Eric Wheeler <[email protected]>
1 parent 35d012a commit e339b72

File tree

9 files changed

+355
-4
lines changed

9 files changed

+355
-4
lines changed

src/core/upgrade/upgrade.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { isTaskHistoryMigrationNeeded, migrateTaskHistoryStorage } from "../task-persistence/taskHistory"
2+
3+
/**
4+
* Checks if any upgrades are needed in the system.
5+
* Currently checks for task history migration needs.
6+
*
7+
* @returns A promise that resolves to true if any upgrades are needed, false otherwise.
8+
*/
9+
export async function isUpgradeNeeded(): Promise<boolean> {
10+
// Check if task history migration is needed
11+
const taskHistoryMigrationNeeded = await isTaskHistoryMigrationNeeded()
12+
13+
// Return true if any upgrade is needed
14+
return taskHistoryMigrationNeeded
15+
}
16+
17+
/**
18+
* Performs all necessary upgrades in the system.
19+
* Currently handles task history migration.
20+
*
21+
* @param logs Optional array to capture log messages
22+
* @returns A promise that resolves to true if upgrades were performed, false if no upgrades were needed.
23+
*/
24+
export async function performUpgrade(logs: string[] = []): Promise<boolean> {
25+
// Check if task history migration is needed
26+
const taskHistoryMigrationNeeded = await isTaskHistoryMigrationNeeded()
27+
28+
// Perform task history migration if needed
29+
if (taskHistoryMigrationNeeded) {
30+
await migrateTaskHistoryStorage(logs)
31+
return true
32+
}
33+
34+
// No upgrades were needed
35+
return false
36+
}

src/core/webview/webviewMessageHandler.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
HistoryItem,
1717
} from "@roo-code/types"
1818
import { getHistoryItemsForSearch } from "../task-persistence/taskHistory"
19+
import { isUpgradeNeeded, performUpgrade } from "../upgrade/upgrade"
1920
import { CloudService } from "@roo-code/cloud"
2021
import { TelemetryService } from "@roo-code/telemetry"
2122
import { type ApiMessage } from "../task-persistence/apiMessages"
@@ -67,6 +68,66 @@ export const webviewMessageHandler = async (
6768
const getGlobalState = <K extends keyof GlobalState>(key: K) => provider.contextProxy.getValue(key)
6869
const updateGlobalState = async <K extends keyof GlobalState>(key: K, value: GlobalState[K]) =>
6970
await provider.contextProxy.setValue(key, value)
71+
72+
/**
73+
* Helper function to handle common functionality for task history operations
74+
* @param operationName Name of the operation for logging
75+
* @param options Options for the operation
76+
* @param operation The async function to perform
77+
* @param onSuccess Callback for successful operation
78+
* @param onError Callback for operation error
79+
* @param logMessageType Type of message to use when sending logs to UI
80+
*/
81+
async function handleLoggingOperation<T>(
82+
operationName: string,
83+
options: any,
84+
operation: (options: any, logs: string[]) => Promise<T>,
85+
onSuccess: (result: T) => Promise<void>,
86+
onError: (error: any) => Promise<void>,
87+
logMessageType: "loggingOperation",
88+
): Promise<void> {
89+
try {
90+
// Create a logs array to capture messages
91+
const logs: string[] = []
92+
93+
// Log the options for debugging
94+
console.log(`[webviewMessageHandler] ${operationName} options:`, JSON.stringify(options, null, 2))
95+
96+
// Create a monitoring function to send logs to UI
97+
const sendLogsToUI = () => {
98+
if (logs.length > 0) {
99+
const logsCopy = [...logs]
100+
logs.length = 0 // Clear the array
101+
102+
// Send each log message to the webview
103+
for (const log of logsCopy) {
104+
provider.postMessageToWebview({
105+
type: logMessageType,
106+
log,
107+
})
108+
}
109+
}
110+
}
111+
112+
// Set up interval to forward logs during operation
113+
const logInterval = setInterval(sendLogsToUI, 100)
114+
115+
// Perform the operation
116+
const result = await operation(options, logs)
117+
118+
// Clear the interval
119+
clearInterval(logInterval)
120+
121+
// Send any remaining logs
122+
sendLogsToUI()
123+
124+
// Handle success
125+
await onSuccess(result)
126+
} catch (error) {
127+
// Handle error
128+
await onError(error)
129+
}
130+
}
70131

71132
/**
72133
* Shared utility to find message indices based on timestamp
@@ -2215,6 +2276,65 @@ export const webviewMessageHandler = async (
22152276
break
22162277
}
22172278

2279+
case "isUpgradeNeeded": {
2280+
try {
2281+
const needed = await isUpgradeNeeded()
2282+
provider.postMessageToWebview({
2283+
type: "upgradeStatus" as any,
2284+
values: {
2285+
needed,
2286+
},
2287+
})
2288+
} catch (error) {
2289+
console.error(`[Upgrade] webviewMessageHandler: Error in isUpgradeNeeded:`, error)
2290+
provider.postMessageToWebview({
2291+
type: "upgradeStatus" as any,
2292+
values: {
2293+
needed: false,
2294+
},
2295+
})
2296+
}
2297+
break
2298+
}
2299+
2300+
case "performUpgrade": {
2301+
await handleLoggingOperation<{ success: boolean }>(
2302+
"performUpgrade",
2303+
{},
2304+
async (_, logs) => {
2305+
return { success: await performUpgrade(logs) }
2306+
},
2307+
async (result) => {
2308+
// Then send upgradeComplete message
2309+
provider.postMessageToWebview({
2310+
type: "upgradeComplete" as any,
2311+
values: {
2312+
success: result.success,
2313+
},
2314+
})
2315+
2316+
// Finally, send upgradeStatus with needed=false to indicate upgrade is no longer needed
2317+
provider.postMessageToWebview({
2318+
type: "upgradeStatus" as any,
2319+
values: {
2320+
needed: false,
2321+
},
2322+
})
2323+
},
2324+
async (error) => {
2325+
provider.postMessageToWebview({
2326+
type: "upgradeComplete" as any,
2327+
values: {
2328+
success: false,
2329+
error: String(error),
2330+
},
2331+
})
2332+
},
2333+
"loggingOperation",
2334+
)
2335+
break
2336+
}
2337+
22182338
case "switchTab": {
22192339
if (message.tab) {
22202340
// Capture tab shown event for all switchTab messages (which are user-initiated)

src/extension.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,6 @@ export async function activate(context: vscode.ExtensionContext) {
7171
context.subscriptions.push(outputChannel)
7272
outputChannel.appendLine(`${Package.name} extension activated - ${JSON.stringify(Package)}`)
7373

74-
// Initialize and migrate task history storage
75-
// (migrateTaskHistoryStorage also calls initializeTaskHistory internally)
76-
await migrateTaskHistoryStorage()
77-
7874
// Migrate old settings to new
7975
await migrateSettings(context, outputChannel)
8076

src/shared/ExtensionMessage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ export interface ExtensionMessage {
109109
| "showDeleteMessageDialog"
110110
| "showEditMessageDialog"
111111
| "taskDeletedConfirmation"
112+
| "loggingOperation"
113+
| "upgradeStatus"
114+
| "upgradeComplete"
112115
text?: string
113116
payload?: any // Add a generic payload for now, can refine later
114117
action?:
@@ -153,6 +156,7 @@ export interface ExtensionMessage {
153156
value?: any
154157
hasContent?: boolean // For checkRulesDirectoryResult
155158
items?: MarketplaceItem[] | HistoryItem[]
159+
log?: string
156160
userInfo?: CloudUserInfo
157161
organizationAllowList?: OrganizationAllowList
158162
tab?: string

src/shared/WebviewMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export interface WebviewMessage {
3030
| "currentApiConfigName"
3131
| "saveApiConfiguration"
3232
| "getHistoryItems"
33+
| "isUpgradeNeeded"
34+
| "performUpgrade"
3335
| "upsertApiConfiguration"
3436
| "deleteApiConfiguration"
3537
| "loadApiConfiguration"

webview-ui/src/App.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEvent } from "react-use"
33
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
44

55
import { ExtensionMessage } from "@roo/ExtensionMessage"
6+
import UpgradeHandler, { useUpgradeCheck } from "./components/upgrade/UpgradeHandler"
67
import TranslationProvider from "./i18n/TranslationContext"
78
import { MarketplaceViewStateManager } from "./components/marketplace/MarketplaceViewStateManager"
89

@@ -79,6 +80,7 @@ const App = () => {
7980

8081
const [showAnnouncement, setShowAnnouncement] = useState(false)
8182
const [tab, setTab] = useState<Tab>("chat")
83+
const { upgradeNeeded, clearUpgradeNeeded } = useUpgradeCheck()
8284

8385
const [humanRelayDialogState, setHumanRelayDialogState] = useState<HumanRelayDialogState>({
8486
isOpen: false,
@@ -213,6 +215,20 @@ const App = () => {
213215

214216
// Do not conditionally load ChatView, it's expensive and there's state we
215217
// don't want to lose (user input, disableInput, askResponse promise, etc.)
218+
219+
// Return early while checking for an upgrade because
220+
// there may be structures that should not be accessed
221+
// until the upgrade completes.
222+
if (upgradeNeeded === null) {
223+
return null
224+
}
225+
226+
// If an upgrade is needed, show the upgrade UI
227+
if (upgradeNeeded) {
228+
return <UpgradeHandler onComplete={clearUpgradeNeeded} />
229+
}
230+
231+
// Normal rendering when no upgrade is needed
216232
return showWelcome ? (
217233
<WelcomeView />
218234
) : (

webview-ui/src/__tests__/App.spec.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ vi.mock("@src/components/account/AccountView", () => ({
8686
},
8787
}))
8888

89+
vi.mock("@src/components/upgrade/UpgradeHandler", () => ({
90+
__esModule: true,
91+
default: () => <div data-testid="upgrade-handler" />,
92+
useUpgradeCheck: () => ({
93+
upgradeNeeded: false,
94+
clearUpgradeNeeded: vi.fn(),
95+
}),
96+
}))
97+
8998
const mockUseExtensionState = vi.fn()
9099

91100
vi.mock("@src/context/ExtensionStateContext", () => ({

0 commit comments

Comments
 (0)