Fix Message Pane Performance (#18314)
* add virtualization to message pane * consolidate resizing * move grid size to props to avoid manually resizing * set messages at fixed intervals * clean up code for 'complete' event * message grid styling * revert result set resize related changes * fix region text * fix msg grid height * fix cell height
This commit is contained in:
Родитель
a5ae4be25f
Коммит
4bca545551
|
@ -83,6 +83,7 @@
|
|||
"@eslint/js": "^9.5.0",
|
||||
"@fluentui/react-components": "^9.54.13",
|
||||
"@fluentui/react-list-preview": "^0.3.6",
|
||||
"@fluentui-contrib/react-data-grid-react-window": "^1.2.0",
|
||||
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
||||
"@jgoz/esbuild-plugin-typecheck": "^4.0.0",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
const pd = require("pretty-data").pd;
|
||||
|
||||
const deletionTimeoutTime = 1.8e6; // in ms, currently 30 minutes
|
||||
const MESSAGE_INTERVAL_IN_MS = 300;
|
||||
|
||||
// holds information about the state of a query runner
|
||||
export class QueryRunnerState {
|
||||
|
@ -56,6 +57,7 @@ export class SqlOutputContentProvider {
|
|||
private _panels = new Map<string, WebviewPanelController>();
|
||||
private _queryResultWebviewController: QueryResultWebviewController;
|
||||
private _executionPlanOptions: ExecutionPlanOptions = {};
|
||||
private _lastSendMessageTime: number;
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
constructor(
|
||||
|
@ -407,6 +409,7 @@ export class SqlOutputContentProvider {
|
|||
if (!this.isRichExperiencesEnabled) {
|
||||
this._panels.get(uri).proxy.sendEvent("start", panelUri);
|
||||
} else {
|
||||
this._lastSendMessageTime = Date.now();
|
||||
this._queryResultWebviewController.addQueryResultState(
|
||||
uri,
|
||||
this.getIsExecutionPlan(),
|
||||
|
@ -476,14 +479,18 @@ export class SqlOutputContentProvider {
|
|||
this._queryResultWebviewController
|
||||
.getQueryResultState(uri)
|
||||
.messages.push(message);
|
||||
this._queryResultWebviewController.getQueryResultState(
|
||||
uri,
|
||||
).tabStates.resultPaneTab = QueryResultPaneTabs.Messages;
|
||||
this._queryResultWebviewController.state =
|
||||
this._queryResultWebviewController.getQueryResultState(
|
||||
uri,
|
||||
);
|
||||
vscode.commands.executeCommand("queryResult.focus");
|
||||
|
||||
// Set state for messages at fixed intervals to avoid spamming the webview
|
||||
if (
|
||||
this._lastSendMessageTime <
|
||||
Date.now() - MESSAGE_INTERVAL_IN_MS
|
||||
) {
|
||||
this._queryResultWebviewController.state =
|
||||
this._queryResultWebviewController.getQueryResultState(
|
||||
uri,
|
||||
);
|
||||
this._lastSendMessageTime = Date.now();
|
||||
}
|
||||
}
|
||||
});
|
||||
queryRunner.eventEmitter.on(
|
||||
|
@ -511,15 +518,6 @@ export class SqlOutputContentProvider {
|
|||
),
|
||||
isError: hasError,
|
||||
});
|
||||
this._queryResultWebviewController.getQueryResultState(
|
||||
uri,
|
||||
).tabStates.resultPaneTab =
|
||||
QueryResultPaneTabs.Messages;
|
||||
this._queryResultWebviewController.state =
|
||||
this._queryResultWebviewController.getQueryResultState(
|
||||
uri,
|
||||
);
|
||||
vscode.commands.executeCommand("queryResult.focus");
|
||||
const tabState =
|
||||
Object.keys(
|
||||
this._queryResultWebviewController.getQueryResultState(
|
||||
|
@ -535,6 +533,7 @@ export class SqlOutputContentProvider {
|
|||
this._queryResultWebviewController.getQueryResultState(
|
||||
uri,
|
||||
);
|
||||
vscode.commands.executeCommand("queryResult.focus");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
@ -9,18 +9,19 @@ import {
|
|||
Link,
|
||||
Tab,
|
||||
TabList,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableColumnDefinition,
|
||||
TableColumnSizingOptions,
|
||||
TableRow,
|
||||
createTableColumn,
|
||||
makeStyles,
|
||||
shorthands,
|
||||
useTableColumnSizing_unstable,
|
||||
useTableFeatures,
|
||||
} from "@fluentui/react-components";
|
||||
import {
|
||||
DataGridBody,
|
||||
DataGrid,
|
||||
DataGridRow,
|
||||
DataGridCell,
|
||||
RowRenderer,
|
||||
} from "@fluentui-contrib/react-data-grid-react-window";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { OpenFilled } from "@fluentui/react-icons";
|
||||
import { QueryResultContext } from "./queryResultStateProvider";
|
||||
|
@ -84,13 +85,9 @@ const useStyles = makeStyles({
|
|||
},
|
||||
},
|
||||
messagesRows: {
|
||||
height: "18px",
|
||||
lineHeight: "18px",
|
||||
fontSize: "12px",
|
||||
flexDirection: "row",
|
||||
...shorthands.padding("10px"),
|
||||
"> *": {
|
||||
marginRight: "10px",
|
||||
},
|
||||
borderBottom: "none",
|
||||
},
|
||||
noResultMessage: {
|
||||
|
@ -122,19 +119,10 @@ export const QueryResultPane = () => {
|
|||
>();
|
||||
webViewState;
|
||||
var metadata = state?.state;
|
||||
const columnsDef: TableColumnDefinition<qr.IMessage>[] = [
|
||||
createTableColumn({
|
||||
columnId: "time",
|
||||
renderHeaderCell: () => <>{locConstants.queryResult.timestamp}</>,
|
||||
}),
|
||||
createTableColumn({
|
||||
columnId: "message",
|
||||
renderHeaderCell: () => <>{locConstants.queryResult.message}</>,
|
||||
}),
|
||||
];
|
||||
const resultPaneParentRef = useRef<HTMLDivElement>(null);
|
||||
const ribbonRef = useRef<HTMLDivElement>(null);
|
||||
const gridParentRef = useRef<HTMLDivElement>(null);
|
||||
const [messageGridHeight, setMessageGridHeight] = useState(0);
|
||||
// Resize grid when parent element resizes
|
||||
useEffect(() => {
|
||||
let gridCount = 0;
|
||||
|
@ -158,6 +146,7 @@ export const QueryResultPane = () => {
|
|||
resultPaneParent,
|
||||
ribbonRef.current,
|
||||
);
|
||||
setMessageGridHeight(availableHeight);
|
||||
if (resultPaneParent.clientWidth && availableHeight) {
|
||||
const gridHeight = calculateGridHeight(
|
||||
gridCount,
|
||||
|
@ -220,40 +209,9 @@ export const QueryResultPane = () => {
|
|||
// gridCount is 1
|
||||
return resultPaneParent.clientWidth - ACTIONBAR_WIDTH_PX;
|
||||
};
|
||||
const [columns] =
|
||||
useState<TableColumnDefinition<qr.IMessage>[]>(columnsDef);
|
||||
const items = metadata?.messages ?? [];
|
||||
|
||||
const sizingOptions: TableColumnSizingOptions = {
|
||||
time: {
|
||||
minWidth: 100,
|
||||
idealWidth: 100,
|
||||
defaultWidth: 100,
|
||||
},
|
||||
message: {
|
||||
minWidth: 500,
|
||||
idealWidth: 500,
|
||||
defaultWidth: 500,
|
||||
},
|
||||
};
|
||||
|
||||
const [columnSizingOption] =
|
||||
useState<TableColumnSizingOptions>(sizingOptions);
|
||||
const { getRows, columnSizing_unstable, tableRef } = useTableFeatures(
|
||||
{
|
||||
columns,
|
||||
items: items,
|
||||
},
|
||||
[
|
||||
useTableColumnSizing_unstable({
|
||||
columnSizingOptions: columnSizingOption,
|
||||
}),
|
||||
],
|
||||
);
|
||||
const rows = getRows();
|
||||
|
||||
//#region Result Grid
|
||||
const gridRefs = useRef<ResultGridHandle[]>([]);
|
||||
|
||||
const renderGrid = (
|
||||
batchId: number,
|
||||
resultId: number,
|
||||
|
@ -360,7 +318,103 @@ export const QueryResultPane = () => {
|
|||
}
|
||||
return grids;
|
||||
};
|
||||
//#endregion
|
||||
|
||||
//#region Message Grid
|
||||
const columnsDef: TableColumnDefinition<qr.IMessage>[] = [
|
||||
createTableColumn({
|
||||
columnId: "time",
|
||||
renderHeaderCell: () => <>{locConstants.queryResult.timestamp}</>,
|
||||
renderCell: (item) => (
|
||||
<>{item.batchId === undefined ? item.time : null}</>
|
||||
),
|
||||
}),
|
||||
createTableColumn({
|
||||
columnId: "message",
|
||||
renderHeaderCell: () => <>{locConstants.queryResult.message}</>,
|
||||
renderCell: (item) => {
|
||||
if (item.link?.text && item.selection) {
|
||||
return (
|
||||
<div>
|
||||
{item.message}{" "}
|
||||
<Link
|
||||
onClick={async () => {
|
||||
await webViewState.extensionRpc.call(
|
||||
"setEditorSelection",
|
||||
{
|
||||
uri: metadata?.uri,
|
||||
selectionData: item.selection,
|
||||
},
|
||||
);
|
||||
}}
|
||||
inline
|
||||
style={{ fontSize: "12px" }}
|
||||
>
|
||||
{item?.link?.text}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <>{item.message}</>;
|
||||
}
|
||||
},
|
||||
}),
|
||||
];
|
||||
const renderRow: RowRenderer<qr.IMessage> = ({ item, rowId }, style) => (
|
||||
<DataGridRow<qr.IMessage>
|
||||
key={rowId}
|
||||
style={style}
|
||||
className={classes.messagesRows}
|
||||
>
|
||||
{({ renderCell }) => (
|
||||
<DataGridCell focusMode="group" style={{ minHeight: "18px" }}>
|
||||
{renderCell(item)}
|
||||
</DataGridCell>
|
||||
)}
|
||||
</DataGridRow>
|
||||
);
|
||||
|
||||
const [columns] =
|
||||
useState<TableColumnDefinition<qr.IMessage>[]>(columnsDef);
|
||||
const items = metadata?.messages ?? [];
|
||||
|
||||
const sizingOptions: TableColumnSizingOptions = {
|
||||
time: {
|
||||
minWidth: 100,
|
||||
idealWidth: 100,
|
||||
defaultWidth: 100,
|
||||
},
|
||||
message: {
|
||||
minWidth: 500,
|
||||
idealWidth: 500,
|
||||
defaultWidth: 500,
|
||||
},
|
||||
};
|
||||
|
||||
const [columnSizingOption] =
|
||||
useState<TableColumnSizingOptions>(sizingOptions);
|
||||
|
||||
const renderMessageGrid = () => {
|
||||
return (
|
||||
<DataGrid
|
||||
items={items}
|
||||
columns={columns}
|
||||
focusMode="cell"
|
||||
resizableColumns
|
||||
columnSizingOptions={columnSizingOption}
|
||||
>
|
||||
<DataGridBody<qr.IMessage>
|
||||
itemSize={18}
|
||||
height={messageGridHeight}
|
||||
>
|
||||
{renderRow}
|
||||
</DataGridBody>
|
||||
</DataGrid>
|
||||
);
|
||||
};
|
||||
//#endregion
|
||||
|
||||
//#region Query Plan
|
||||
useEffect(() => {
|
||||
// gets execution plans
|
||||
if (
|
||||
|
@ -373,6 +427,7 @@ export const QueryResultPane = () => {
|
|||
state.provider.getExecutionPlan(metadata.uri);
|
||||
}
|
||||
}, [metadata?.executionPlanState?.xmlPlans]);
|
||||
//#endregion
|
||||
|
||||
return !metadata || !hasResultsOrMessages(metadata) ? (
|
||||
<div>
|
||||
|
@ -463,66 +518,7 @@ export const QueryResultPane = () => {
|
|||
uri: metadata?.uri,
|
||||
})}
|
||||
>
|
||||
<Table
|
||||
size="small"
|
||||
as="table"
|
||||
{...columnSizing_unstable.getTableProps()}
|
||||
ref={tableRef}
|
||||
>
|
||||
<TableBody>
|
||||
{rows.map((row, index) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={index}
|
||||
className={classes.messagesRows}
|
||||
>
|
||||
<TableCell
|
||||
{...columnSizing_unstable.getTableCellProps(
|
||||
"time",
|
||||
)}
|
||||
>
|
||||
{row.item.batchId === undefined
|
||||
? row.item.time
|
||||
: null}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
{...columnSizing_unstable.getTableCellProps(
|
||||
"message",
|
||||
)}
|
||||
>
|
||||
{row.item.message}
|
||||
{row.item.link?.text &&
|
||||
row.item.selection && (
|
||||
<>
|
||||
{" "}
|
||||
<Link
|
||||
onClick={async () => {
|
||||
await webViewState.extensionRpc.call(
|
||||
"setEditorSelection",
|
||||
{
|
||||
uri: metadata?.uri,
|
||||
selectionData:
|
||||
row
|
||||
.item
|
||||
.selection,
|
||||
},
|
||||
);
|
||||
}}
|
||||
>
|
||||
{
|
||||
row.item
|
||||
?.link
|
||||
?.text
|
||||
}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{renderMessageGrid()}
|
||||
</div>
|
||||
)}
|
||||
{metadata.tabStates!.resultPaneTab ===
|
||||
|
|
34
yarn.lock
34
yarn.lock
|
@ -337,6 +337,12 @@
|
|||
jsonwebtoken "^9.0.0"
|
||||
uuid "^8.3.0"
|
||||
|
||||
"@babel/runtime@^7.0.0":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.9.tgz#65884fd6dc255a775402cc1d9811082918f4bf00"
|
||||
integrity sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
"@babel/code-frame@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.9.tgz#895b6c7e04a7271a0cbfd575d2e8131751914cc7"
|
||||
|
@ -748,6 +754,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.4.tgz#1d459cee5031893a08a0e064c406ad2130cced7c"
|
||||
integrity sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==
|
||||
|
||||
"@fluentui-contrib/react-data-grid-react-window@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@fluentui-contrib/react-data-grid-react-window/-/react-data-grid-react-window-1.2.0.tgz#4cca74a81c6cf13fb310ad63a72f8521cc4de32a"
|
||||
integrity sha512-PMAQvRVsZBl8Ej7MrE+9lsuDyEc+tW/tg5zqTz017jo5/E3IoFPEJs5UpRDo3mHmrqtQo06UY79DzxK+N4oBaA==
|
||||
dependencies:
|
||||
"@swc/helpers" "~0.5.1"
|
||||
react-window "^1.8.5"
|
||||
|
||||
"@fluentui/keyboard-keys@^9.0.7":
|
||||
version "9.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@fluentui/keyboard-keys/-/keyboard-keys-9.0.7.tgz#a603ea0827ccd48ab606fd48ff259b8a914d23d4"
|
||||
|
@ -2249,6 +2263,13 @@
|
|||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@swc/helpers@~0.5.1":
|
||||
version "0.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.13.tgz#33e63ff3cd0cade557672bd7888a39ce7d115a8c"
|
||||
integrity sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
|
@ -8540,6 +8561,11 @@ mdurl@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0"
|
||||
integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==
|
||||
|
||||
"memoize-one@>=3.1.1 <6":
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||
|
||||
memoizee@^0.4.15:
|
||||
version "0.4.17"
|
||||
resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.17.tgz#942a5f8acee281fa6fb9c620bddc57e3b7382949"
|
||||
|
@ -10209,6 +10235,14 @@ react-transition-group@^4.4.1:
|
|||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-window@^1.8.5:
|
||||
version "1.8.10"
|
||||
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.10.tgz#9e6b08548316814b443f7002b1cf8fd3a1bdde03"
|
||||
integrity sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.0.0"
|
||||
memoize-one ">=3.1.1 <6"
|
||||
|
||||
react@^18.3.1:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
|
||||
|
|
Загрузка…
Ссылка в новой задаче