* 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:
Hai Cao 2024-11-06 12:39:50 -08:00 коммит произвёл GitHub
Родитель a5ae4be25f
Коммит 4bca545551
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
4 изменённых файлов: 160 добавлений и 130 удалений

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

@ -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 ===

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

@ -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"