Skip to content

Commit 913d211

Browse files
authored
Fixing execution plan loading (#18005)
* Fixing execution plan loading * moving api status to its own file
1 parent df11eb4 commit 913d211

File tree

8 files changed

+90
-49
lines changed

8 files changed

+90
-49
lines changed

src/connectionconfig/connectionDialogWebViewController.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import * as vscode from 'vscode';
77
import { ReactWebViewPanelController } from "../controllers/reactWebviewController";
8-
import { ApiStatus, AuthenticationType, ConnectionDialogReducers, ConnectionDialogWebviewState, FormComponent, FormComponentActionButton, FormComponentOptions, FormComponentType, FormTabType, IConnectionDialogProfile } from '../sharedInterfaces/connectionDialog';
8+
import { AuthenticationType, ConnectionDialogReducers, ConnectionDialogWebviewState, FormComponent, FormComponentActionButton, FormComponentOptions, FormComponentType, FormTabType, IConnectionDialogProfile } from '../sharedInterfaces/connectionDialog';
99
import { IConnectionInfo } from 'vscode-mssql';
1010
import MainController from '../controllers/mainController';
1111
import { getConnectionDisplayName } from '../models/connectionInfo';
@@ -17,6 +17,7 @@ import { ConnectionOption } from 'azdata';
1717
import { Logger } from '../models/logger';
1818
import VscodeWrapper from '../controllers/vscodeWrapper';
1919
import * as LocalizedConstants from '../constants/localizedConstants';
20+
import { ApiStatus } from '../sharedInterfaces/webview';
2021

2122
export class ConnectionDialogWebViewController extends ReactWebViewPanelController<ConnectionDialogWebviewState, ConnectionDialogReducers> {
2223
private _connectionToEditCopy: IConnectionDialogProfile | undefined;

src/controllers/executionPlanWebviewController.ts

+27-12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { homedir } from "os";
1212
import { exists } from "../utils/utils";
1313
import UntitledSqlDocumentService from '../controllers/untitledSqlDocumentService';
1414
import * as path from 'path';
15+
import { ApiStatus } from "../sharedInterfaces/webview";
1516

1617
export class ExecutionPlanWebViewController extends ReactWebViewPanelController<
1718
ep.ExecutionPlanWebViewState,
@@ -30,7 +31,15 @@ export class ExecutionPlanWebViewController extends ReactWebViewPanelController<
3031
context,
3132
`${xmlPlanFileName}`, // Sets the webview title
3233
WebviewRoute.executionPlanDocument,
33-
{},
34+
{
35+
sqlPlanContent: executionPlanContents,
36+
theme: vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark ? "dark" : "light",
37+
localizedConstants: LocalizedConstants,
38+
loadState: ApiStatus.Loading,
39+
executionPlan: undefined,
40+
executionPlanGraphs: [],
41+
totalCost: 0,
42+
},
3443
vscode.ViewColumn.Active,
3544
{
3645
dark: vscode.Uri.joinPath(
@@ -45,25 +54,19 @@ export class ExecutionPlanWebViewController extends ReactWebViewPanelController<
4554
),
4655
}
4756
);
48-
this.state.isLoading = true;
4957
this.initialize();
5058
}
5159

5260
private async initialize() {
53-
this.state.sqlPlanContent = this.executionPlanContents;
54-
this.state.theme = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark ? "dark" : "light";
55-
this.state.localizedConstants = LocalizedConstants;
56-
61+
this.state.loadState = ApiStatus.Loading;
62+
this.updateState();
5763
await this.createExecutionPlanGraphs();
58-
this.state.totalCost = this.calculateTotalCost();
5964
this.registerRpcHandlers();
60-
this.state.isLoading = false;
6165
}
6266

6367
private registerRpcHandlers() {
6468
this.registerReducer("getExecutionPlan", async (state, payload) => {
6569
await this.createExecutionPlanGraphs();
66-
6770
return {
6871
...state,
6972
executionPlan: this.state.executionPlan,
@@ -141,10 +144,18 @@ export class ExecutionPlanWebViewController extends ReactWebViewPanelController<
141144
graphFileContent: this.executionPlanContents,
142145
graphFileType: ".sqlplan",
143146
};
144-
this.state.executionPlan =
145-
await this.executionPlanService.getExecutionPlan(planFile);
146-
this.state.executionPlanGraphs = this.state.executionPlan.graphs;
147+
try {
148+
this.state.executionPlan =
149+
await this.executionPlanService.getExecutionPlan(planFile);
150+
this.state.executionPlanGraphs = this.state.executionPlan.graphs;
151+
this.state.loadState = ApiStatus.Loaded;
152+
this.state.totalCost = this.calculateTotalCost();
153+
} catch (e) {
154+
this.state.loadState = ApiStatus.Error;
155+
this.state.errorMessage = e.toString();
156+
}
147157
}
158+
this.updateState();
148159
}
149160

150161
private calculateTotalCost(): number {
@@ -154,4 +165,8 @@ export class ExecutionPlanWebViewController extends ReactWebViewPanelController<
154165
}
155166
return sum;
156167
}
168+
169+
private updateState() {
170+
this.state = this.state;
171+
}
157172
}

src/reactviews/pages/ConnectionDialog/connectionFormPage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import {
1717
import { Dismiss24Regular } from "@fluentui/react-icons";
1818

1919
import { ConnectionDialogContext } from "./connectionDialogStateProvider";
20-
import { ApiStatus } from "../../../sharedInterfaces/connectionDialog";
2120
import { FormField, useFormStyles } from "../../common/forms/formUtils";
21+
import { ApiStatus } from "../../../sharedInterfaces/webview";
2222

2323
export const ConnectionFormPage = () => {
2424
const connectionDialogContext = useContext(ConnectionDialogContext);

src/reactviews/pages/ConnectionDialog/connectionStringPage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Button, MessageBar, Spinner } from "@fluentui/react-components";
7-
import { ApiStatus } from "../../../sharedInterfaces/connectionDialog";
87
import { useContext } from "react";
98
import { ConnectionDialogContext } from "./connectionDialogStateProvider";
109
import { generateFormField, useFormStyles } from "../../common/forms/formUtils";
10+
import { ApiStatus } from "../../../sharedInterfaces/webview";
1111

1212
export const ConnectionStringPage = () => {
1313
const connectionDialogContext = useContext(ConnectionDialogContext);

src/reactviews/pages/ExecutionPlan/executionPlanInterfaces.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { ApiStatus } from "../../../sharedInterfaces/webview";
7+
68
export interface ExecutionPlanWebViewState {
79
sqlPlanContent?: string;
810
executionPlan?: GetExecutionPlanResult;
911
executionPlanGraphs?: ExecutionPlanGraph[];
1012
theme?: string;
1113
totalCost?: number;
12-
isLoading?: boolean;
14+
loadState?: ApiStatus;
1315
localizedConstants?: any;
16+
errorMessage?: string;
1417
}
1518

1619
export interface ExecutionPlanReducers {

src/reactviews/pages/ExecutionPlan/executionPlanPage.tsx

+42-27
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { useContext, useEffect, useState } from 'react';
6+
import { useContext } from 'react';
77
import { ExecutionPlanContext } from "./executionPlanStateProvider";
8-
import { makeStyles, Spinner } from '@fluentui/react-components';
8+
import { makeStyles, Spinner, Text } from '@fluentui/react-components';
99
import { ExecutionPlanGraph } from './executionPlanGraph';
10+
import { ErrorCircleRegular } from "@fluentui/react-icons";
11+
import { ApiStatus } from '../../../sharedInterfaces/webview';
1012

1113
const useStyles = makeStyles({
1214
outerDiv: {
@@ -20,40 +22,53 @@ const useStyles = makeStyles({
2022
width: "100%",
2123
display: "flex",
2224
justifyContent: "center",
23-
alignItems: "center"
25+
alignItems: "center",
26+
flexDirection: "column",
27+
padding: "20px"
28+
},
29+
errorIcon: {
30+
fontSize: '100px',
31+
opacity: 0.5
2432
}
2533
})
2634

2735
export const ExecutionPlanPage = () => {
2836
const classes = useStyles();
29-
const state = useContext(ExecutionPlanContext);
30-
const [executionPlanState, setExecutionPlanState] = useState(state?.state);
31-
32-
useEffect(() => {
33-
function checkIfStateIsLoaded() {
34-
if (!executionPlanState && state?.state) {
35-
setExecutionPlanState(state.state);
36-
}
37+
const provider = useContext(ExecutionPlanContext);
38+
const loadState = provider?.state?.loadState ?? ApiStatus.Loading;
39+
const renderMainContent = () => {
40+
switch (loadState) {
41+
case ApiStatus.Loading:
42+
return (
43+
<div className={classes.spinnerDiv}>
44+
<Spinner
45+
label="Loading execution plan..."
46+
labelPosition="below"
47+
/>
48+
</div>
49+
);
50+
case ApiStatus.Loaded:
51+
const executionPlanGraphs = provider?.state?.executionPlanGraphs ?? [];
52+
return (
53+
executionPlanGraphs?.map((_, index) => (
54+
<ExecutionPlanGraph key={index} graphIndex={index} />
55+
))
56+
);
57+
case ApiStatus.Error:
58+
return (
59+
<div className={classes.spinnerDiv}>
60+
<ErrorCircleRegular className={classes.errorIcon} />
61+
<Text size={400}>{provider?.state?.errorMessage ?? ''}</Text>
62+
</div>
63+
);
3764
}
38-
39-
// Check every 200 milliseconds
40-
const intervalId = setInterval(checkIfStateIsLoaded, 200);
41-
42-
return () => clearInterval(intervalId);
43-
}, [executionPlanState, state]);
65+
}
4466

4567
return (
4668
<div className={classes.outerDiv}>
47-
{executionPlanState && !executionPlanState.isLoading && executionPlanState.executionPlanGraphs ? (
48-
executionPlanState.executionPlanGraphs.map((_, index) => (
49-
<ExecutionPlanGraph key={index} graphIndex={index} />
50-
))
51-
) : (
52-
// localize this
53-
<div className={classes.spinnerDiv}>
54-
<Spinner label="Loading..." labelPosition="below" />
55-
</div>
56-
)}
69+
{
70+
renderMainContent()
71+
}
5772
</div>
5873
);
5974
};

src/sharedInterfaces/connectionDialog.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { Theme } from "@fluentui/react-components";
77
import * as vscodeMssql from "vscode-mssql";
8+
import { ApiStatus } from "./webview";
89

910
export interface ConnectionDialogWebviewState {
1011
selectedFormTab: FormTabType;
@@ -33,12 +34,7 @@ export interface IConnectionDialogProfile extends vscodeMssql.IConnectionInfo {
3334
azureAuthType?: vscodeMssql.AzureAuthType;
3435
}
3536

36-
export enum ApiStatus {
37-
NotStarted = 'notStarted',
38-
Loading = 'loading',
39-
Loaded = 'loaded',
40-
Error = 'error',
41-
}
37+
4238

4339
export interface FormContextProps<T> {
4440
formAction: (event: FormEvent<T>) => void;

src/sharedInterfaces/webview.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
export enum ApiStatus {
7+
NotStarted = 'notStarted',
8+
Loading = 'loading',
9+
Loaded = 'loaded',
10+
Error = 'error',
11+
}

0 commit comments

Comments
 (0)