Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 136 additions & 136 deletions extensions/ql-vscode/src/view/results/raw-results-table.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
ResultTableProps,
className,
emptyQueryResultsMessage,
jumpToLocation,
Expand All @@ -19,158 +19,158 @@ import { onNavigation } from "./results";
import { tryGetResolvableLocation } from "../../common/bqrs-utils";
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
import { sendTelemetry } from "../common/telemetry";
import { assertNever } from "../../common/helpers-pure";

export type RawTableProps = ResultTableProps & {
export type RawTableProps = {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why we were requiring ResultTableProps as props but then barely accessing any of the members. I've changed this to just require databaseUri. The place where we call RawTable we do {...this.props} and this still works since unknown props are just ignored.

databaseUri: string;
resultSet: RawTableResultSet;
sortState?: RawResultsSortState;
offset: number;
};

interface RawTableState {
selectedItem?: { row: number; column: number };
interface TableItem {
readonly row: number;
readonly column: number;
}

export class RawTable extends React.Component<RawTableProps, RawTableState> {
private scroller = new ScrollIntoViewHelper();

constructor(props: RawTableProps) {
super(props);
this.setSelection = this.setSelection.bind(this);
this.handleNavigationEvent = this.handleNavigationEvent.bind(this);
this.state = {};
export function RawTable({
databaseUri,
resultSet,
sortState,
offset,
}: RawTableProps) {
const [selectedItem, setSelectedItem] = useState<TableItem | undefined>();

const scroller = useRef<ScrollIntoViewHelper | undefined>(undefined);
if (scroller.current === undefined) {
scroller.current = new ScrollIntoViewHelper();
}
useEffect(() => scroller.current?.update());

private setSelection(row: number, column: number) {
this.setState((prev) => ({
...prev,
selectedItem: { row, column },
}));
const setSelection = useCallback((row: number, column: number): void => {
setSelectedItem({ row, column });
sendTelemetry("local-results-raw-results-table-selected");
}

render(): React.ReactNode {
const { resultSet, databaseUri } = this.props;

let dataRows = resultSet.rows;
if (dataRows.length === 0) {
return emptyQueryResultsMessage();
}

let numTruncatedResults = 0;
if (dataRows.length > RAW_RESULTS_LIMIT) {
numTruncatedResults = dataRows.length - RAW_RESULTS_LIMIT;
dataRows = dataRows.slice(0, RAW_RESULTS_LIMIT);
}

const tableRows = dataRows.map((row: ResultRow, rowIndex: number) => (
<RawTableRow
key={rowIndex}
rowIndex={rowIndex + this.props.offset}
row={row}
databaseUri={databaseUri}
selectedColumn={
this.state.selectedItem?.row === rowIndex
? this.state.selectedItem?.column
: undefined
}, []);

const navigateWithDelta = useCallback(
(rowDelta: number, columnDelta: number): void => {
setSelectedItem((prevSelectedItem) => {
const numberOfAlerts = resultSet.rows.length;
if (numberOfAlerts === 0) {
return prevSelectedItem;
}
onSelected={this.setSelection}
scroller={this.scroller}
/>
));

if (numTruncatedResults > 0) {
const colSpan = dataRows[0].length + 1; // one row for each data column, plus index column
tableRows.push(
<tr>
<td
key={"message"}
colSpan={colSpan}
style={{ textAlign: "center", fontStyle: "italic" }}
>
Too many results to show at once. {numTruncatedResults} result(s)
omitted.
</td>
</tr>,
);
}

return (
<table className={className}>
<RawTableHeader
columns={resultSet.schema.columns}
schemaName={resultSet.schema.name}
sortState={this.props.sortState}
/>
<tbody>{tableRows}</tbody>
</table>
);
}

private handleNavigationEvent(event: NavigateMsg) {
switch (event.direction) {
case NavigationDirection.up: {
this.navigateWithDelta(-1, 0);
break;
}
case NavigationDirection.down: {
this.navigateWithDelta(1, 0);
break;
}
case NavigationDirection.left: {
this.navigateWithDelta(0, -1);
break;
}
case NavigationDirection.right: {
this.navigateWithDelta(0, 1);
break;
const currentRow = prevSelectedItem?.row;
const nextRow = currentRow === undefined ? 0 : currentRow + rowDelta;
if (nextRow < 0 || nextRow >= numberOfAlerts) {
return prevSelectedItem;
}
const currentColumn = prevSelectedItem?.column;
const nextColumn =
currentColumn === undefined ? 0 : currentColumn + columnDelta;
// Jump to the location of the new cell
const rowData = resultSet.rows[nextRow];
if (nextColumn < 0 || nextColumn >= rowData.length) {
return prevSelectedItem;
}
const cellData = rowData[nextColumn];
if (cellData != null && typeof cellData === "object") {
const location = tryGetResolvableLocation(cellData.url);
if (location !== undefined) {
jumpToLocation(location, databaseUri);
}
}
scroller.current?.scrollIntoViewOnNextUpdate();
return { row: nextRow, column: nextColumn };
});
},
[databaseUri, resultSet, scroller],
);

const handleNavigationEvent = useCallback(
(event: NavigateMsg) => {
switch (event.direction) {
case NavigationDirection.up: {
navigateWithDelta(-1, 0);
break;
}
case NavigationDirection.down: {
navigateWithDelta(1, 0);
break;
}
case NavigationDirection.left: {
navigateWithDelta(0, -1);
break;
}
case NavigationDirection.right: {
navigateWithDelta(0, 1);
break;
}
default:
assertNever(event.direction);
}
},
[navigateWithDelta],
);

useEffect(() => {
onNavigation.addListener(handleNavigationEvent);
return () => {
onNavigation.removeListener(handleNavigationEvent);
};
}, [handleNavigationEvent]);

const [dataRows, numTruncatedResults] = useMemo(() => {
if (resultSet.rows.length <= RAW_RESULTS_LIMIT) {
return [resultSet.rows, 0];
}
return [
resultSet.rows.slice(0, RAW_RESULTS_LIMIT),
resultSet.rows.length - RAW_RESULTS_LIMIT,
];
}, [resultSet]);

if (dataRows.length === 0) {
return emptyQueryResultsMessage();
}

private navigateWithDelta(rowDelta: number, columnDelta: number) {
this.setState((prevState) => {
const numberOfAlerts = this.props.resultSet.rows.length;
if (numberOfAlerts === 0) {
return prevState;
}
const currentRow = prevState.selectedItem?.row;
const nextRow = currentRow === undefined ? 0 : currentRow + rowDelta;
if (nextRow < 0 || nextRow >= numberOfAlerts) {
return prevState;
}
const currentColumn = prevState.selectedItem?.column;
const nextColumn =
currentColumn === undefined ? 0 : currentColumn + columnDelta;
// Jump to the location of the new cell
const rowData = this.props.resultSet.rows[nextRow];
if (nextColumn < 0 || nextColumn >= rowData.length) {
return prevState;
const tableRows = dataRows.map((row: ResultRow, rowIndex: number) => (
<RawTableRow
key={rowIndex}
rowIndex={rowIndex + offset}
row={row}
databaseUri={databaseUri}
selectedColumn={
selectedItem?.row === rowIndex ? selectedItem?.column : undefined
}
const cellData = rowData[nextColumn];
if (cellData != null && typeof cellData === "object") {
const location = tryGetResolvableLocation(cellData.url);
if (location !== undefined) {
jumpToLocation(location, this.props.databaseUri);
}
}
this.scroller.scrollIntoViewOnNextUpdate();
return {
...prevState,
selectedItem: { row: nextRow, column: nextColumn },
};
});
}

componentDidUpdate() {
this.scroller.update();
}

componentDidMount() {
this.scroller.update();
onNavigation.addListener(this.handleNavigationEvent);
onSelected={setSelection}
scroller={scroller.current}
/>
));

if (numTruncatedResults > 0) {
const colSpan = dataRows[0].length + 1; // one row for each data column, plus index column
tableRows.push(
<tr>
<td
key={"message"}
colSpan={colSpan}
style={{ textAlign: "center", fontStyle: "italic" }}
>
Too many results to show at once. {numTruncatedResults} result(s)
omitted.
</td>
</tr>,
);
}

componentWillUnmount() {
onNavigation.removeListener(this.handleNavigationEvent);
}
return (
<table className={className}>
<RawTableHeader
columns={resultSet.schema.columns}
schemaName={resultSet.schema.name}
sortState={sortState}
/>
<tbody>{tableRows}</tbody>
</table>
);
}