Skip to content

Commit

Permalink
✨ Implement Flux-style message passing (konveyor#125)
Browse files Browse the repository at this point in the history
Changes:
1. convert ServerState enum to union type - enums are not native JS
    constructs and are not correctly packaged in the current build
2. unify data flow from extension to the webview - the entire state
    object is sent to keep the state atomic
3. provide type safe action creators (Redux-style)
4. provide generic map-based message handler similar to Redux reducers

Signed-off-by: Radoslaw Szwajkowski <[email protected]>
  • Loading branch information
rszwajko authored Dec 2, 2024
1 parent b1dc8bd commit 9f828ef
Show file tree
Hide file tree
Showing 17 changed files with 417 additions and 410 deletions.
2 changes: 1 addition & 1 deletion shared/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "./types/";
export * from "./types/index";
25 changes: 25 additions & 0 deletions shared/src/types/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export const SET_STATE = "SET_STATE";
export const START_ANALYSIS = "START_ANALYSIS";
export const START_SERVER = "START_SERVER";
export const CANCEL_SOLUTION = "CANCEL_SOLUTION";
export const GET_SOLUTION = "GET_SOLUTION";
export const OPEN_FILE = "OPEN_FILE";
export const VIEW_FIX = "VIEW_FIX";
export const APPLY_FILE = "APPLY_FILE";
export const DISCARD_FILE = "DISCARD_FILE";

export type WebviewActionType =
| typeof SET_STATE
| typeof START_ANALYSIS
| typeof START_SERVER
| typeof CANCEL_SOLUTION
| typeof GET_SOLUTION
| typeof OPEN_FILE
| typeof VIEW_FIX
| typeof APPLY_FILE
| typeof DISCARD_FILE;

export interface WebviewAction<S, T> {
type: S;
payload: T;
}
117 changes: 2 additions & 115 deletions shared/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,115 +1,2 @@
import { Uri } from "vscode";

export interface Incident {
uri: string;
lineNumber: number;
severity: "High" | "Medium" | "Low";
message: string;
codeSnip: string;
}

export interface Link {
url: string;
title?: string;
}

export enum Category {
Potential = "potential",
Optional = "optional",
Mandatory = "mandatory",
}

export interface Violation {
description: string;
category?: Category;
labels?: string[];
incidents: Incident[];
links?: Link[];
extras?: unknown;
effort?: number;
}

export interface RuleSet {
name?: string;
description?: string;
tags?: string[];
violations?: { [key: string]: Violation };
insights?: { [key: string]: Violation };
errors?: { [key: string]: string };
unmatched?: string[];
skipped?: string[];
}

// KaiConfigModels type definition
export interface KaiConfigModels {
provider: string;
args: Record<string, any>;
template?: string;
llamaHeader?: boolean;
llmRetries: number;
llmRetryDelay: number;
}

// KaiRpcApplicationConfig type definition
export interface KaiInitializeParams {
rootPath: string;
modelProvider: KaiConfigModels;
kaiBackendUrl: string;

logLevel: string;
stderrLogLevel: string;
fileLogLevel?: string;
logDirPath?: string;

analyzerLspLspPath: string;
analyzerLspRpcPath: string;
analyzerLspRulesPath: string;
analyzerLspJavaBundlePath: string;
}

export interface GetSolutionParams {
file_path: string;
incidents: Incident[];
}
export interface Change {
// relative file path before the change, may be empty if file was created in this change
original: string;
// relative file path after the change, may be empty if file was deleted in this change
modified: string;
// diff in unified format - tested with git diffs
diff: string;
}

export interface GetSolutionResult {
encountered_errors: string[];
changes: Change[];
scope: Scope;
}

export interface LocalChange {
modifiedUri: Uri;
originalUri: Uri;
diff: string;
state: "pending" | "applied" | "discarded";
}

export interface ResolutionMessage {
type: string;
solution: Solution;
violation: Violation;
incident: Incident;
isRelevantSolution: boolean;
}

export interface SolutionResponse {
diff: string;
encountered_errors: string[];
modified_files: string[];
}

export interface Scope {
incident: Incident;
violation?: Violation;
}

export type Solution = GetSolutionResult | SolutionResponse;
export * from "./actions";
export * from "./types";
135 changes: 135 additions & 0 deletions shared/src/types/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Uri } from "vscode";

export interface Incident {
uri: string;
lineNumber: number;
severity: "High" | "Medium" | "Low";
message: string;
codeSnip: string;
}

export interface Link {
url: string;
title?: string;
}

export enum Category {
Potential = "potential",
Optional = "optional",
Mandatory = "mandatory",
}

export interface Violation {
description: string;
category?: Category;
labels?: string[];
incidents: Incident[];
links?: Link[];
extras?: unknown;
effort?: number;
}

export interface RuleSet {
name?: string;
description?: string;
tags?: string[];
violations?: { [key: string]: Violation };
insights?: { [key: string]: Violation };
errors?: { [key: string]: string };
unmatched?: string[];
skipped?: string[];
}

// KaiConfigModels type definition
export interface KaiConfigModels {
provider: string;
args: Record<string, any>;
template?: string;
llamaHeader?: boolean;
llmRetries: number;
llmRetryDelay: number;
}

// KaiRpcApplicationConfig type definition
export interface KaiInitializeParams {
rootPath: string;
modelProvider: KaiConfigModels;
kaiBackendUrl: string;

logLevel: string;
stderrLogLevel: string;
fileLogLevel?: string;
logDirPath?: string;

analyzerLspLspPath: string;
analyzerLspRpcPath: string;
analyzerLspRulesPath: string;
analyzerLspJavaBundlePath: string;
}

export interface GetSolutionParams {
file_path: string;
incidents: Incident[];
}
export interface Change {
// relative file path before the change, may be empty if file was created in this change
original: string;
// relative file path after the change, may be empty if file was deleted in this change
modified: string;
// diff in unified format - tested with git diffs
diff: string;
}

export interface GetSolutionResult {
encountered_errors: string[];
changes: Change[];
scope: Scope;
}

export interface LocalChange {
modifiedUri: Uri;
originalUri: Uri;
diff: string;
state: "pending" | "applied" | "discarded";
}

export interface ResolutionMessage {
type: string;
solution: Solution;
violation: Violation;
incident: Incident;
isRelevantSolution: boolean;
}

export interface SolutionResponse {
diff: string;
encountered_errors: string[];
modified_files: string[];
}

export interface Scope {
incident: Incident;
violation?: Violation;
}

export type Solution = GetSolutionResult | SolutionResponse;

export interface ExtensionData {
localChanges: LocalChange[];
ruleSets: RuleSet[];
resolutionPanelData: any;
isAnalyzing: boolean;
isFetchingSolution: boolean;
isStartingServer: boolean;
serverState: ServerState;
solutionData?: Solution;
solutionScope?: Scope;
}

export type ServerState =
| "initial"
| "starting"
| "startFailed"
| "running"
| "stopping"
| "stopped";
10 changes: 5 additions & 5 deletions vscode/src/KonveyorGUIWebviewViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class KonveyorGUIWebviewViewProvider implements WebviewViewProvider {
if (this._viewType === KonveyorGUIWebviewViewProvider.RESOLUTION_VIEW_TYPE) {
const savedData = this._extensionState.data.resolutionPanelData;
if (savedData) {
this._panel.webview.postMessage({ type: "loadResolutionState", data: savedData });
this._panel.webview.postMessage(this._extensionState.data);
}
}

Expand All @@ -75,10 +75,10 @@ export class KonveyorGUIWebviewViewProvider implements WebviewViewProvider {
// Assuming the analysis webview is tracked and can be accessed via the ExtensionState or similar
const sidebarProvider = this._extensionState.webviewProviders.get("sidebar");
if (sidebarProvider?.webview && sidebarProvider._isWebviewReady) {
sidebarProvider.webview.postMessage({
type: "solutionConfirmation",
data: { confirmed: true, solution: null },
});
// sidebarProvider.webview.postMessage({
// type: "solutionConfirmation",
// data: { confirmed: true, solution: null },
// });
} else {
console.error("Analysis webview is not ready or not available.");
}
Expand Down
15 changes: 8 additions & 7 deletions vscode/src/client/analyzerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import * as rpc from "vscode-jsonrpc/node";
import path from "path";
import { Incident, RuleSet, SolutionResponse, Violation } from "@editor-extensions/shared";
import { buildDataFolderPath } from "../data";
import { ExtensionData, ExtensionState, ServerState } from "../extensionState";
import { ExtensionState } from "../extensionState";
import { ExtensionData, ServerState } from "@editor-extensions/shared";
import { setTimeout } from "timers/promises";

const exec = util.promisify(callbackExec);
Expand All @@ -30,7 +31,7 @@ export class AnalyzerClient {
this.fireStateChange = (state: ServerState) =>
mutateExtensionState((draft) => {
draft.serverState = state;
draft.isStartingServer = state === ServerState.Starting;
draft.isStartingServer = state === "starting";
});
this.fireAnalysisStateChange = (flag: boolean) =>
mutateExtensionState((draft) => {
Expand Down Expand Up @@ -59,7 +60,7 @@ export class AnalyzerClient {
return;
}

this.fireStateChange(ServerState.Starting);
this.fireStateChange("starting");
this.outputChannel.appendLine(`Starting the server ...`);

this.kaiRpcServer = spawn(this.getKaiRpcServerPath(), this.getKaiRpcServerArgs(), {
Expand All @@ -85,14 +86,14 @@ export class AnalyzerClient {

// Stops the analyzer server
public stop(): void {
this.fireStateChange(ServerState.Stopping);
this.fireStateChange("stopping");
this.outputChannel.appendLine(`Stopping the server ...`);
if (this.kaiRpcServer) {
this.kaiRpcServer.kill();
}
this.rpcConnection?.dispose();
this.kaiRpcServer = null;
this.fireStateChange(ServerState.Stopped);
this.fireStateChange("stopped");
this.outputChannel.appendLine(`Server stopped`);
}

Expand Down Expand Up @@ -159,7 +160,7 @@ export class AnalyzerClient {
);
this.outputChannel.appendLine(`${response}`);
progress.report({ message: "RPC Server initialized" });
this.fireStateChange(ServerState.Running);
this.fireStateChange("running");
return;
} catch (err: any) {
this.outputChannel.appendLine(`Error: ${err}`);
Expand All @@ -168,7 +169,7 @@ export class AnalyzerClient {
}
}
progress.report({ message: "Kai initialization failed!" });
this.fireStateChange(ServerState.StartFailed);
this.fireStateChange("startFailed");
},
);
}
Expand Down
4 changes: 2 additions & 2 deletions vscode/src/diffView/register.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as vscode from "vscode";
import { KonveyorTreeDataProvider } from "./fileModel";
import { Navigation } from "./navigation";
import { ExtensionData, ExtensionState } from "src/extensionState";
import { ExtensionState } from "src/extensionState";
import { KONVEYOR_READ_ONLY_SCHEME, KONVEYOR_SCHEME } from "../utilities";
import KonveyorReadOnlyProvider from "../data/readOnlyStorage";
import { Immutable } from "immer";
import { LocalChange } from "@editor-extensions/shared";
import { LocalChange, ExtensionData } from "@editor-extensions/shared";

export function registerDiffView({
extensionContext: context,
Expand Down
Loading

0 comments on commit 9f828ef

Please sign in to comment.