Skip to content

Commit 9955be5

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 28e33a3 commit 9955be5

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
@@ -2225,6 +2286,65 @@ export const webviewMessageHandler = async (
22252286
break
22262287
}
22272288

2289+
case "isUpgradeNeeded": {
2290+
try {
2291+
const needed = await isUpgradeNeeded()
2292+
provider.postMessageToWebview({
2293+
type: "upgradeStatus" as any,
2294+
values: {
2295+
needed,
2296+
},
2297+
})
2298+
} catch (error) {
2299+
console.error(`[Upgrade] webviewMessageHandler: Error in isUpgradeNeeded:`, error)
2300+
provider.postMessageToWebview({
2301+
type: "upgradeStatus" as any,
2302+
values: {
2303+
needed: false,
2304+
},
2305+
})
2306+
}
2307+
break
2308+
}
2309+
2310+
case "performUpgrade": {
2311+
await handleLoggingOperation<{ success: boolean }>(
2312+
"performUpgrade",
2313+
{},
2314+
async (_, logs) => {
2315+
return { success: await performUpgrade(logs) }
2316+
},
2317+
async (result) => {
2318+
// Then send upgradeComplete message
2319+
provider.postMessageToWebview({
2320+
type: "upgradeComplete" as any,
2321+
values: {
2322+
success: result.success,
2323+
},
2324+
})
2325+
2326+
// Finally, send upgradeStatus with needed=false to indicate upgrade is no longer needed
2327+
provider.postMessageToWebview({
2328+
type: "upgradeStatus" as any,
2329+
values: {
2330+
needed: false,
2331+
},
2332+
})
2333+
},
2334+
async (error) => {
2335+
provider.postMessageToWebview({
2336+
type: "upgradeComplete" as any,
2337+
values: {
2338+
success: false,
2339+
error: String(error),
2340+
},
2341+
})
2342+
},
2343+
"loggingOperation",
2344+
)
2345+
break
2346+
}
2347+
22282348
case "switchTab": {
22292349
if (message.tab) {
22302350
// 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
@@ -107,6 +107,9 @@ export interface ExtensionMessage {
107107
| "codeIndexSettingsSaved"
108108
| "codeIndexSecretStatus"
109109
| "taskDeletedConfirmation"
110+
| "loggingOperation"
111+
| "upgradeStatus"
112+
| "upgradeComplete"
110113
text?: string
111114
payload?: any // Add a generic payload for now, can refine later
112115
action?:
@@ -151,6 +154,7 @@ export interface ExtensionMessage {
151154
value?: any
152155
hasContent?: boolean // For checkRulesDirectoryResult
153156
items?: MarketplaceItem[] | HistoryItem[]
157+
log?: string
154158
userInfo?: CloudUserInfo
155159
organizationAllowList?: OrganizationAllowList
156160
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

@@ -55,6 +56,7 @@ const App = () => {
5556

5657
const [showAnnouncement, setShowAnnouncement] = useState(false)
5758
const [tab, setTab] = useState<Tab>("chat")
59+
const { upgradeNeeded, clearUpgradeNeeded } = useUpgradeCheck()
5860

5961
const [humanRelayDialogState, setHumanRelayDialogState] = useState<{
6062
isOpen: boolean
@@ -168,6 +170,20 @@ const App = () => {
168170

169171
// Do not conditionally load ChatView, it's expensive and there's state we
170172
// don't want to lose (user input, disableInput, askResponse promise, etc.)
173+
174+
// Return early while checking for an upgrade because
175+
// there may be structures that should not be accessed
176+
// until the upgrade completes.
177+
if (upgradeNeeded === null) {
178+
return null
179+
}
180+
181+
// If an upgrade is needed, show the upgrade UI
182+
if (upgradeNeeded) {
183+
return <UpgradeHandler onComplete={clearUpgradeNeeded} />
184+
}
185+
186+
// Normal rendering when no upgrade is needed
171187
return showWelcome ? (
172188
<WelcomeView />
173189
) : (

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)