Move ResultTablesHeader to its own component
This commit is contained in:
141
extensions/ql-vscode/src/view/results/ResultTablesHeader.tsx
Normal file
141
extensions/ql-vscode/src/view/results/ResultTablesHeader.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import * as React from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { vscode } from "../vscode-api";
|
||||
import { openFile, tableHeaderItemClassName } from "./result-table-utils";
|
||||
import { sendTelemetry } from "../common/telemetry";
|
||||
import {
|
||||
ALERTS_TABLE_NAME,
|
||||
ParsedResultSets,
|
||||
} from "../../common/interface-types";
|
||||
import { basename } from "../../common/path";
|
||||
|
||||
interface Props {
|
||||
queryName: string;
|
||||
queryPath: string;
|
||||
parsedResultSets: ParsedResultSets;
|
||||
selectedTable: string;
|
||||
}
|
||||
|
||||
export function ResultTablesHeader(props: Props) {
|
||||
const { queryPath, queryName, parsedResultSets, selectedTable } = props;
|
||||
|
||||
const [selectedPage, setSelectedPage] = React.useState(
|
||||
`${parsedResultSets.pageNumber + 1}`,
|
||||
);
|
||||
useEffect(() => {
|
||||
setSelectedPage(`${parsedResultSets.pageNumber + 1}`);
|
||||
}, [parsedResultSets.pageNumber]);
|
||||
|
||||
// FIXME: The extension, not the view, should be in charge of deciding whether to initially show
|
||||
// a raw or alerts page. We have to conditionally recompute the number of pages here, because
|
||||
// on initial load of query results, resultSets.numPages will have the number of *raw* pages available,
|
||||
// not interpreted pages, because the extension doesn't know the view will default to showing alerts
|
||||
// instead.
|
||||
const numPages = Math.max(
|
||||
selectedTable === ALERTS_TABLE_NAME
|
||||
? parsedResultSets.numInterpretedPages
|
||||
: parsedResultSets.numPages,
|
||||
1,
|
||||
);
|
||||
|
||||
const onChangeHandler = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSelectedPage(e.target.value);
|
||||
sendResultsPageChangedTelemetry();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const changePage = useCallback(
|
||||
(value: string) => {
|
||||
const pageNumber = parseInt(value);
|
||||
if (pageNumber !== undefined && !isNaN(pageNumber)) {
|
||||
const actualPageNumber = Math.max(
|
||||
0,
|
||||
Math.min(pageNumber - 1, numPages - 1),
|
||||
);
|
||||
vscode.postMessage({
|
||||
t: "changePage",
|
||||
pageNumber: actualPageNumber,
|
||||
selectedTable,
|
||||
});
|
||||
}
|
||||
},
|
||||
[numPages, selectedTable],
|
||||
);
|
||||
|
||||
const onBlurHandler = useCallback(
|
||||
(e: React.FocusEvent<HTMLInputElement, Element>) => {
|
||||
changePage(e.target.value);
|
||||
},
|
||||
[changePage],
|
||||
);
|
||||
|
||||
const onKeyDownHandler = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
changePage(e.currentTarget.value);
|
||||
},
|
||||
[changePage],
|
||||
);
|
||||
|
||||
const prevPageHandler = useCallback(() => {
|
||||
vscode.postMessage({
|
||||
t: "changePage",
|
||||
pageNumber: Math.max(parsedResultSets.pageNumber - 1, 0),
|
||||
selectedTable,
|
||||
});
|
||||
sendResultsPageChangedTelemetry();
|
||||
}, [parsedResultSets.pageNumber, selectedTable]);
|
||||
|
||||
const nextPageHandler = useCallback(() => {
|
||||
vscode.postMessage({
|
||||
t: "changePage",
|
||||
pageNumber: Math.min(parsedResultSets.pageNumber + 1, numPages - 1),
|
||||
selectedTable,
|
||||
});
|
||||
sendResultsPageChangedTelemetry();
|
||||
}, [numPages, parsedResultSets.pageNumber, selectedTable]);
|
||||
|
||||
const openQueryHandler = useCallback(() => {
|
||||
openFile(queryPath);
|
||||
sendTelemetry("local-results-open-query-file");
|
||||
}, [queryPath]);
|
||||
|
||||
return (
|
||||
<span className="vscode-codeql__table-selection-pagination">
|
||||
<button onClick={prevPageHandler}>«</button>
|
||||
<input
|
||||
type="number"
|
||||
size={3}
|
||||
value={selectedPage}
|
||||
min="1"
|
||||
max={numPages}
|
||||
onChange={onChangeHandler}
|
||||
onBlur={onBlurHandler}
|
||||
onKeyDown={onKeyDownHandler}
|
||||
/>
|
||||
<span>/ {numPages}</span>
|
||||
<button value=">" onClick={nextPageHandler}>
|
||||
»
|
||||
</button>
|
||||
<div className={tableHeaderItemClassName}>{queryName}</div>
|
||||
<div className={tableHeaderItemClassName}>
|
||||
{/*
|
||||
eslint-disable-next-line
|
||||
jsx-a11y/anchor-is-valid
|
||||
*/}
|
||||
<a
|
||||
href="#"
|
||||
onClick={openQueryHandler}
|
||||
className="vscode-codeql__result-table-location-link"
|
||||
>
|
||||
Open {basename(queryPath)}
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function sendResultsPageChangedTelemetry() {
|
||||
sendTelemetry("local-results-alert-table-page-changed");
|
||||
}
|
||||
@@ -23,11 +23,10 @@ import {
|
||||
tableHeaderItemClassName,
|
||||
toggleDiagnosticsClassName,
|
||||
alertExtrasClassName,
|
||||
openFile,
|
||||
} from "./result-table-utils";
|
||||
import { vscode } from "../vscode-api";
|
||||
import { sendTelemetry } from "../common/telemetry";
|
||||
import { basename } from "../../common/path";
|
||||
import { ResultTablesHeader } from "./ResultTablesHeader";
|
||||
|
||||
/**
|
||||
* Properties for the `ResultTables` component.
|
||||
@@ -52,7 +51,6 @@ interface ResultTablesProps {
|
||||
*/
|
||||
interface ResultTablesState {
|
||||
selectedTable: string; // name of selected result set
|
||||
selectedPage: string; // stringified selected page
|
||||
problemsViewSelected: boolean;
|
||||
}
|
||||
|
||||
@@ -138,10 +136,8 @@ export class ResultTables extends React.Component<
|
||||
getDefaultResultSet(
|
||||
getResultSets(props.rawResultSets, props.interpretation),
|
||||
);
|
||||
const selectedPage = `${props.parsedResultSets.pageNumber + 1}`;
|
||||
this.state = {
|
||||
selectedTable,
|
||||
selectedPage,
|
||||
problemsViewSelected: false,
|
||||
};
|
||||
}
|
||||
@@ -168,10 +164,7 @@ export class ResultTables extends React.Component<
|
||||
getResultSets(props.rawResultSets, props.interpretation),
|
||||
);
|
||||
|
||||
return {
|
||||
selectedTable,
|
||||
selectedPage: `${props.parsedResultSets.pageNumber + 1}`,
|
||||
};
|
||||
return { selectedTable };
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -245,103 +238,6 @@ export class ResultTables extends React.Component<
|
||||
sendTelemetry("local-results-alert-table-page-changed");
|
||||
}
|
||||
|
||||
renderPageButtons(): JSX.Element {
|
||||
const { parsedResultSets } = this.props;
|
||||
const selectedTable = this.state.selectedTable;
|
||||
|
||||
// FIXME: The extension, not the view, should be in charge of deciding whether to initially show
|
||||
// a raw or alerts page. We have to conditionally recompute the number of pages here, because
|
||||
// on initial load of query results, resultSets.numPages will have the number of *raw* pages available,
|
||||
// not interpreted pages, because the extension doesn't know the view will default to showing alerts
|
||||
// instead.
|
||||
const numPages = Math.max(
|
||||
selectedTable === ALERTS_TABLE_NAME
|
||||
? parsedResultSets.numInterpretedPages
|
||||
: parsedResultSets.numPages,
|
||||
1,
|
||||
);
|
||||
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ selectedPage: e.target.value });
|
||||
this.sendResultsPageChangedTelemetry();
|
||||
};
|
||||
const choosePage = (input: string) => {
|
||||
const pageNumber = parseInt(input);
|
||||
if (pageNumber !== undefined && !isNaN(pageNumber)) {
|
||||
const actualPageNumber = Math.max(
|
||||
0,
|
||||
Math.min(pageNumber - 1, numPages - 1),
|
||||
);
|
||||
vscode.postMessage({
|
||||
t: "changePage",
|
||||
pageNumber: actualPageNumber,
|
||||
selectedTable,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const prevPage = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
vscode.postMessage({
|
||||
t: "changePage",
|
||||
pageNumber: Math.max(parsedResultSets.pageNumber - 1, 0),
|
||||
selectedTable,
|
||||
});
|
||||
this.sendResultsPageChangedTelemetry();
|
||||
};
|
||||
const nextPage = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
vscode.postMessage({
|
||||
t: "changePage",
|
||||
pageNumber: Math.min(parsedResultSets.pageNumber + 1, numPages - 1),
|
||||
selectedTable,
|
||||
});
|
||||
this.sendResultsPageChangedTelemetry();
|
||||
};
|
||||
|
||||
const openQuery = () => {
|
||||
openFile(this.props.queryPath);
|
||||
sendTelemetry("local-results-open-query-file");
|
||||
};
|
||||
const fileName = basename(this.props.queryPath);
|
||||
|
||||
return (
|
||||
<span className="vscode-codeql__table-selection-pagination">
|
||||
<button onClick={prevPage}>«</button>
|
||||
<input
|
||||
type="number"
|
||||
size={3}
|
||||
value={this.state.selectedPage}
|
||||
min="1"
|
||||
max={numPages}
|
||||
onChange={onChange}
|
||||
onBlur={(e) => choosePage(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.keyCode === 13) {
|
||||
choosePage((e.target as HTMLInputElement).value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span>/ {numPages}</span>
|
||||
<button value=">" onClick={nextPage}>
|
||||
»
|
||||
</button>
|
||||
<div className={tableHeaderItemClassName}>{this.props.queryName}</div>
|
||||
<div className={tableHeaderItemClassName}>
|
||||
{/*
|
||||
eslint-disable-next-line
|
||||
jsx-a11y/anchor-is-valid
|
||||
*/}
|
||||
<a
|
||||
href="#"
|
||||
onClick={openQuery}
|
||||
className="vscode-codeql__result-table-location-link"
|
||||
>
|
||||
Open {fileName}
|
||||
</a>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
const { selectedTable } = this.state;
|
||||
const resultSets = getResultSets(
|
||||
@@ -369,7 +265,10 @@ export class ResultTables extends React.Component<
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
{this.renderPageButtons()}
|
||||
<ResultTablesHeader
|
||||
{...this.props}
|
||||
selectedTable={this.state.selectedTable}
|
||||
/>
|
||||
<div className={tableHeaderClassName}></div>
|
||||
<div className={tableHeaderClassName}>
|
||||
<select value={selectedTable} onChange={this.onTableSelectionChange}>
|
||||
|
||||
Reference in New Issue
Block a user