This commit is contained in:
Hai Cao 2024-11-06 15:25:39 -08:00
Родитель 04ac9ecec7
Коммит 77fac43446
3 изменённых файлов: 265 добавлений и 396 удалений

Просмотреть файл

@ -20,14 +20,10 @@ import UntitledSqlDocumentService from "../controllers/untitledSqlDocumentServic
import { ExecutionPlanService } from "../services/executionPlanService";
import VscodeWrapper from "../controllers/vscodeWrapper";
import { QueryResultWebviewPanelController } from "./queryResultWebviewPanelController";
import { getNewResultPaneViewColumn } from "./utils";
import {
createExecutionPlanGraphs,
saveExecutionPlan,
showPlanXml,
showQuery,
updateTotalCost,
} from "../controllers/sharedExecutionPlanUtils";
getNewResultPaneViewColumn,
registerCommonRequestHandlers,
} from "./utils";
export class QueryResultWebviewController extends ReactWebviewViewController<
qr.QueryResultWebviewState,
@ -173,187 +169,7 @@ export class QueryResultWebviewController extends ReactWebviewViewController<
this.registerRequestHandler("getWebviewLocation", async () => {
return qr.QueryResultWebviewLocation.Panel;
});
this.registerRequestHandler("getRows", async (message) => {
const result =
await this._sqlOutputContentProvider.rowRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.rowStart,
message.numberOfRows,
);
let currentState = this.getQueryResultState(message.uri);
if (
currentState.isExecutionPlan &&
// check if the current result set is the result set that contains the xml plan
currentState.resultSetSummaries[message.batchId][
message.resultId
].columnInfo[0].columnName === Constants.showPlanXmlColumnName
) {
currentState.executionPlanState.xmlPlans =
// this gets the xml plan returned by the get execution
// plan query
currentState.executionPlanState.xmlPlans.concat(
result.rows[0][0].displayValue,
);
}
this.setQueryResultState(message.uri, currentState);
return result;
});
this.registerRequestHandler("setEditorSelection", async (message) => {
return await this._sqlOutputContentProvider.editorSelectionRequestHandler(
message.uri,
message.selectionData,
);
});
this.registerRequestHandler("saveResults", async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.SaveResults,
{
correlationId: this._correlationId,
format: message.format,
selection: message.selection,
origin: message.origin,
},
);
return await this._sqlOutputContentProvider.saveResultsRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.format,
message.selection,
);
});
this.registerRequestHandler("copySelection", async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.CopyResults,
{
correlationId: this._correlationId,
},
);
return await this._sqlOutputContentProvider.copyRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.selection,
);
});
this.registerRequestHandler("copyWithHeaders", async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.CopyResultsHeaders,
{
correlationId: this._correlationId,
},
);
return await this._sqlOutputContentProvider.copyRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.selection,
true, //copy headers flag
);
});
this.registerRequestHandler("copyHeaders", async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.CopyHeaders,
{
correlationId: this._correlationId,
},
);
return await this._sqlOutputContentProvider.copyHeadersRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.selection,
);
});
this.registerReducer("setResultTab", async (state, payload) => {
state.tabStates.resultPaneTab = payload.tabId;
return state;
});
this.registerReducer("getExecutionPlan", async (state, payload) => {
// because this is an overridden call, this makes sure it is being
// called properly
if ("uri" in payload) {
const currentResultState = this.getQueryResultState(
payload.uri,
);
if (
!(
// Check if actual plan is enabled or current result is an execution plan
(
(currentResultState.actualPlanEnabled ||
currentResultState.isExecutionPlan) &&
// Ensure execution plan state exists and execution plan graphs have not loaded
currentResultState.executionPlanState &&
currentResultState.executionPlanState
.executionPlanGraphs.length === 0 &&
// Check for non-empty XML plans and result summaries
currentResultState.executionPlanState.xmlPlans
.length &&
Object.keys(currentResultState.resultSetSummaries)
.length &&
// Verify XML plans match expected number of result sets
currentResultState.executionPlanState.xmlPlans
.length ===
this.getNumExecutionPlanResultSets(
currentResultState.resultSetSummaries,
currentResultState.actualPlanEnabled,
)
)
)
) {
return state;
}
state = (await createExecutionPlanGraphs(
state,
this.executionPlanService,
currentResultState.executionPlanState.xmlPlans,
)) as qr.QueryResultWebviewState;
state.executionPlanState.loadState = ApiStatus.Loaded;
state.tabStates.resultPaneTab =
qr.QueryResultPaneTabs.ExecutionPlan;
return state;
}
});
this.registerReducer("addXmlPlan", async (state, payload) => {
state.executionPlanState.xmlPlans = [
...state.executionPlanState.xmlPlans,
payload.xmlPlan,
];
return state;
});
this.registerReducer("saveExecutionPlan", async (state, payload) => {
return (await saveExecutionPlan(
state,
payload,
)) as qr.QueryResultWebviewState;
});
this.registerReducer("showPlanXml", async (state, payload) => {
return (await showPlanXml(
state,
payload,
)) as qr.QueryResultWebviewState;
});
this.registerReducer("showQuery", async (state, payload) => {
return (await showQuery(
state,
payload,
this.untitledSqlDocumentService,
)) as qr.QueryResultWebviewState;
});
this.registerReducer("updateTotalCost", async (state, payload) => {
return (await updateTotalCost(
state,
payload,
)) as qr.QueryResultWebviewState;
});
registerCommonRequestHandlers(this, this._correlationId);
}
public async createPanelController(uri: string) {
@ -367,15 +183,12 @@ export class QueryResultWebviewController extends ReactWebviewViewController<
const controller = new QueryResultWebviewPanelController(
this._context,
this.executionPlanService,
this.untitledSqlDocumentService,
this._vscodeWrapper,
viewColumn,
uri,
this._queryResultStateMap.get(uri).title,
this,
);
controller.setSqlOutputContentProvider(this._sqlOutputContentProvider);
controller.state = this.getQueryResultState(uri);
controller.revealToForeground();
this._queryResultWebviewPanelControllerMap.set(uri, controller);
@ -467,16 +280,28 @@ export class QueryResultWebviewController extends ReactWebviewViewController<
this._sqlOutputContentProvider = provider;
}
public getSqlOutputContentProvider(): SqlOutputContentProvider {
return this._sqlOutputContentProvider;
}
public setExecutionPlanService(service: ExecutionPlanService): void {
this.executionPlanService = service;
}
public getExecutionPlanService(): ExecutionPlanService {
return this.executionPlanService;
}
public setUntitledDocumentService(
service: UntitledSqlDocumentService,
): void {
this.untitledSqlDocumentService = service;
}
public getUntitledDocumentService(): UntitledSqlDocumentService {
return this.untitledSqlDocumentService;
}
public async copyAllMessagesToClipboard(uri: string): Promise<void> {
const messages = uri
? this.getQueryResultState(uri)?.messages?.map(

Просмотреть файл

@ -6,38 +6,20 @@
import * as vscode from "vscode";
import * as qr from "../sharedInterfaces/queryResult";
// import * as Constants from "../constants/constants";
import { SqlOutputContentProvider } from "../models/sqlOutputContentProvider";
import { sendActionEvent } from "../telemetry/telemetry";
import {
TelemetryActions,
TelemetryViews,
} from "../sharedInterfaces/telemetry";
import { randomUUID } from "crypto";
import { ApiStatus } from "../sharedInterfaces/webview";
import UntitledSqlDocumentService from "../controllers/untitledSqlDocumentService";
import { ExecutionPlanService } from "../services/executionPlanService";
import VscodeWrapper from "../controllers/vscodeWrapper";
import { ReactWebviewPanelController } from "../controllers/reactWebviewPanelController";
import { QueryResultWebviewController } from "./queryResultWebViewController";
import {
createExecutionPlanGraphs,
saveExecutionPlan,
showPlanXml,
showQuery,
updateTotalCost,
} from "../controllers/sharedExecutionPlanUtils";
import { registerCommonRequestHandlers } from "./utils";
export class QueryResultWebviewPanelController extends ReactWebviewPanelController<
qr.QueryResultWebviewState,
qr.QueryResultReducers
> {
private _sqlOutputContentProvider: SqlOutputContentProvider;
private _correlationId: string = randomUUID();
constructor(
context: vscode.ExtensionContext,
private _executionPlanService: ExecutionPlanService,
private _untitledSqlDocumentService: UntitledSqlDocumentService,
private _vscodeWrapper: VscodeWrapper,
private _viewColumn: vscode.ViewColumn,
private _uri: string,
@ -87,188 +69,7 @@ export class QueryResultWebviewPanelController extends ReactWebviewPanelControll
this.registerRequestHandler("getWebviewLocation", async () => {
return qr.QueryResultWebviewLocation.Document;
});
this.registerRequestHandler("getRows", async (message) => {
const result =
await this._sqlOutputContentProvider.rowRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.rowStart,
message.numberOfRows,
);
let currentState =
this._queryResultWebviewViewController.getQueryResultState(
message.uri,
);
if (currentState.isExecutionPlan) {
currentState.executionPlanState.xmlPlans =
// this gets the xml plan returned by the get execution
// plan query
currentState.executionPlanState.xmlPlans.concat(
result.rows[0][0].displayValue,
);
}
this._queryResultWebviewViewController.setQueryResultState(
message.uri,
currentState,
);
return result;
});
this.registerRequestHandler("setEditorSelection", async (message) => {
return await this._sqlOutputContentProvider.editorSelectionRequestHandler(
message.uri,
message.selectionData,
);
});
this.registerRequestHandler("saveResults", async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.SaveResults,
{
correlationId: this._correlationId,
format: message.format,
selection: message.selection,
origin: message.origin,
},
);
return await this._sqlOutputContentProvider.saveResultsRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.format,
message.selection,
);
});
this.registerRequestHandler("copySelection", async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.CopyResults,
{
correlationId: this._correlationId,
},
);
return await this._sqlOutputContentProvider.copyRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.selection,
);
});
this.registerRequestHandler("copyWithHeaders", async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.CopyResultsHeaders,
{
correlationId: this._correlationId,
},
);
return await this._sqlOutputContentProvider.copyRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.selection,
true, //copy headers flag
);
});
this.registerRequestHandler("copyHeaders", async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.CopyHeaders,
{
correlationId: this._correlationId,
},
);
return await this._sqlOutputContentProvider.copyHeadersRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.selection,
);
});
this.registerReducer("setResultTab", async (state, payload) => {
state.tabStates.resultPaneTab = payload.tabId;
return state;
});
this.registerReducer("getExecutionPlan", async (state, payload) => {
// because this is an overridden call, this makes sure it is being
// called properly
if ("uri" in payload) {
const currentResultState =
this._queryResultWebviewViewController.getQueryResultState(
payload.uri,
);
if (
!(
// Check if actual plan is enabled or current result is an execution plan
(
(currentResultState.actualPlanEnabled ||
currentResultState.isExecutionPlan) &&
// Ensure execution plan state exists and execution plan graphs have not loaded
currentResultState.executionPlanState &&
currentResultState.executionPlanState
.executionPlanGraphs.length === 0 &&
// Check for non-empty XML plans and result summaries
currentResultState.executionPlanState.xmlPlans
.length &&
Object.keys(currentResultState.resultSetSummaries)
.length &&
// Verify XML plans match expected number of result sets
currentResultState.executionPlanState.xmlPlans
.length ===
this._queryResultWebviewViewController.getNumExecutionPlanResultSets(
currentResultState.resultSetSummaries,
currentResultState.actualPlanEnabled,
)
)
)
) {
return state;
}
state = (await createExecutionPlanGraphs(
state,
this._executionPlanService,
currentResultState.executionPlanState.xmlPlans,
)) as qr.QueryResultWebviewState;
state.executionPlanState.loadState = ApiStatus.Loaded;
state.tabStates.resultPaneTab =
qr.QueryResultPaneTabs.ExecutionPlan;
return state;
}
});
this.registerReducer("addXmlPlan", async (state, payload) => {
state.executionPlanState.xmlPlans = [
...state.executionPlanState.xmlPlans,
payload.xmlPlan,
];
return state;
});
this.registerReducer("saveExecutionPlan", async (state, payload) => {
return (await saveExecutionPlan(
state,
payload,
)) as qr.QueryResultWebviewState;
});
this.registerReducer("showPlanXml", async (state, payload) => {
return (await showPlanXml(
state,
payload,
)) as qr.QueryResultWebviewState;
});
this.registerReducer("showQuery", async (state, payload) => {
return (await showQuery(
state,
payload,
this._untitledSqlDocumentService,
)) as qr.QueryResultWebviewState;
});
this.registerReducer("updateTotalCost", async (state, payload) => {
return (await updateTotalCost(
state,
payload,
)) as qr.QueryResultWebviewState;
});
registerCommonRequestHandlers(this, this._correlationId);
}
public override extraDispose(): void {
@ -279,9 +80,7 @@ export class QueryResultWebviewPanelController extends ReactWebviewPanelControll
this.panel.reveal(this._viewColumn);
}
public setSqlOutputContentProvider(
provider: SqlOutputContentProvider,
): void {
this._sqlOutputContentProvider = provider;
public getQueryResultWebviewViewController(): QueryResultWebviewController {
return this._queryResultWebviewViewController;
}
}

Просмотреть файл

@ -6,6 +6,22 @@
import VscodeWrapper from "../controllers/vscodeWrapper";
import * as Constants from "../constants/constants";
import * as vscode from "vscode";
import {
TelemetryViews,
TelemetryActions,
} from "../sharedInterfaces/telemetry";
import { ApiStatus } from "../sharedInterfaces/webview";
import {
createExecutionPlanGraphs,
saveExecutionPlan,
showPlanXml,
showQuery,
updateTotalCost,
} from "../controllers/sharedExecutionPlanUtils";
import { sendActionEvent } from "../telemetry/telemetry";
import * as qr from "../sharedInterfaces/queryResult";
import { QueryResultWebviewPanelController } from "./queryResultWebviewPanelController";
import { QueryResultWebviewController } from "./queryResultWebViewController";
export function getNewResultPaneViewColumn(
uri: string,
@ -43,3 +59,232 @@ export function getNewResultPaneViewColumn(
}
return viewColumn;
}
export function registerCommonRequestHandlers(
webviewController:
| QueryResultWebviewController
| QueryResultWebviewPanelController,
correlationId: string,
) {
let webviewViewController: QueryResultWebviewController =
webviewController instanceof QueryResultWebviewController
? webviewController
: webviewController.getQueryResultWebviewViewController();
webviewController.registerRequestHandler("getRows", async (message) => {
const result = await webviewViewController
.getSqlOutputContentProvider()
.rowRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.rowStart,
message.numberOfRows,
);
let currentState = webviewViewController.getQueryResultState(
message.uri,
);
if (
currentState.isExecutionPlan &&
// check if the current result set is the result set that contains the xml plan
currentState.resultSetSummaries[message.batchId][message.resultId]
.columnInfo[0].columnName === Constants.showPlanXmlColumnName
) {
currentState.executionPlanState.xmlPlans =
// this gets the xml plan returned by the get execution
// plan query
currentState.executionPlanState.xmlPlans.concat(
result.rows[0][0].displayValue,
);
}
webviewViewController.setQueryResultState(message.uri, currentState);
return result;
});
webviewController.registerRequestHandler(
"setEditorSelection",
async (message) => {
return await webviewViewController
.getSqlOutputContentProvider()
.editorSelectionRequestHandler(
message.uri,
message.selectionData,
);
},
);
webviewController.registerRequestHandler("saveResults", async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.SaveResults,
{
correlationId: correlationId,
format: message.format,
selection: message.selection,
origin: message.origin,
},
);
return await webviewViewController
.getSqlOutputContentProvider()
.saveResultsRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.format,
message.selection,
);
});
webviewController.registerRequestHandler(
"copySelection",
async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.CopyResults,
{
correlationId: correlationId,
},
);
return await webviewViewController
.getSqlOutputContentProvider()
.copyRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.selection,
);
},
);
webviewController.registerRequestHandler(
"copyWithHeaders",
async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.CopyResultsHeaders,
{
correlationId: correlationId,
format: undefined,
selection: undefined,
origin: undefined,
},
);
return await webviewViewController
.getSqlOutputContentProvider()
.copyRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.selection,
true, //copy headers flag
);
},
);
webviewController.registerRequestHandler("copyHeaders", async (message) => {
sendActionEvent(
TelemetryViews.QueryResult,
TelemetryActions.CopyHeaders,
{
correlationId: correlationId,
},
);
return await webviewViewController
.getSqlOutputContentProvider()
.copyHeadersRequestHandler(
message.uri,
message.batchId,
message.resultId,
message.selection,
);
});
webviewController.registerReducer(
"setResultTab",
async (state, payload) => {
state.tabStates.resultPaneTab = payload.tabId;
return state;
},
);
webviewController.registerReducer(
"getExecutionPlan",
async (state, payload) => {
// because this is an overridden call, this makes sure it is being
// called properly
if ("uri" in payload) {
const currentResultState =
webviewViewController.getQueryResultState(payload.uri);
if (
!(
// Check if actual plan is enabled or current result is an execution plan
(
(currentResultState.actualPlanEnabled ||
currentResultState.isExecutionPlan) &&
// Ensure execution plan state exists and execution plan graphs have not loaded
currentResultState.executionPlanState &&
currentResultState.executionPlanState
.executionPlanGraphs.length === 0 &&
// Check for non-empty XML plans and result summaries
currentResultState.executionPlanState.xmlPlans
.length &&
Object.keys(currentResultState.resultSetSummaries)
.length &&
// Verify XML plans match expected number of result sets
currentResultState.executionPlanState.xmlPlans
.length ===
webviewViewController.getNumExecutionPlanResultSets(
currentResultState.resultSetSummaries,
currentResultState.actualPlanEnabled,
)
)
)
) {
return state;
}
state = (await createExecutionPlanGraphs(
state,
webviewViewController.getExecutionPlanService(),
currentResultState.executionPlanState.xmlPlans,
)) as qr.QueryResultWebviewState;
state.executionPlanState.loadState = ApiStatus.Loaded;
state.tabStates.resultPaneTab =
qr.QueryResultPaneTabs.ExecutionPlan;
return state;
}
},
);
webviewController.registerReducer("addXmlPlan", async (state, payload) => {
state.executionPlanState.xmlPlans = [
...state.executionPlanState.xmlPlans,
payload.xmlPlan,
];
return state;
});
webviewController.registerReducer(
"saveExecutionPlan",
async (state, payload) => {
return (await saveExecutionPlan(
state,
payload,
)) as qr.QueryResultWebviewState;
},
);
webviewController.registerReducer("showPlanXml", async (state, payload) => {
return (await showPlanXml(
state,
payload,
)) as qr.QueryResultWebviewState;
});
webviewController.registerReducer("showQuery", async (state, payload) => {
return (await showQuery(
state,
payload,
webviewViewController.getUntitledDocumentService(),
)) as qr.QueryResultWebviewState;
});
webviewController.registerReducer(
"updateTotalCost",
async (state, payload) => {
return (await updateTotalCost(
state,
payload,
)) as qr.QueryResultWebviewState;
},
);
}