Merge pull request #491 from jcreedcmu/jcreed/cleanup

Remove pagination feature flag
This commit is contained in:
jcreedcmu
2020-07-08 11:31:23 -04:00
committed by GitHub
6 changed files with 44 additions and 258 deletions

View File

@@ -103,30 +103,7 @@ export function adaptBqrs(schema: AdaptedSchema, page: DecodedBqrsChunk): RawRes
};
}
/**
* This type has two branches; we are in the process of changing from
* one to the other. The old way is to parse them inside the webview,
* the new way is to parse them in the extension. The main motivation
* for this transition is to make pagination possible in such a way
* that only one page needs to be sent from the extension to the webview.
*/
export type ParsedResultSets = ExtensionParsedResultSets | WebviewParsedResultSets;
/**
* The old method doesn't require any nontrivial information to be included here,
* just a tag to indicate that it is being used.
*/
export interface WebviewParsedResultSets {
t: 'WebviewParsed';
selectedTable?: string; // when undefined, means 'show default table'
}
/**
* The new method includes which bqrs page is being sent, and the
* actual results parsed on the extension side.
*/
export interface ExtensionParsedResultSets {
t: 'ExtensionParsed';
export interface ParsedResultSets {
pageNumber: number;
numPages: number;
numInterpretedPages: number;

View File

@@ -39,18 +39,6 @@ class Setting {
const ROOT_SETTING = new Setting('codeQL');
// Enable experimental features
/**
* Any settings below are deliberately not in package.json so that
* they do not appear in the settings ui in vscode itself. If users
* want to enable experimental features, they can add them directly in
* their vscode settings json file.
*/
/* Advanced setting: used to enable bqrs parsing in the cli instead of in the webview. */
export const EXPERIMENTAL_BQRS_SETTING = new Setting('experimentalBqrsParsing', ROOT_SETTING);
// Distribution configuration
const DISTRIBUTION_SETTING = new Setting('cli', ROOT_SETTING);

View File

@@ -41,7 +41,6 @@ import {
ParsedResultSets,
RawResultSet,
} from './adapt';
import { EXPERIMENTAL_BQRS_SETTING } from './config';
import {
WebviewReveal,
fileUriToWebviewUri,
@@ -335,40 +334,33 @@ export class InterfaceManager extends DisposableObject {
}
const getParsedResultSets = async (): Promise<ParsedResultSets> => {
if (EXPERIMENTAL_BQRS_SETTING.getValue()) {
const resultSetSchemas = await this.getResultSetSchemas(results);
const resultSetNames = resultSetSchemas.map(schema => schema.name);
// This may not wind up being the page we actually show, if there are interpreted results,
// but speculatively send it anyway.
const selectedTable = getDefaultResultSetName(resultSetNames);
const schema = resultSetSchemas.find(
(resultSet) => resultSet.name == selectedTable
)!;
if (schema === undefined) {
return { t: 'WebviewParsed' };
}
const resultSetSchemas = await this.getResultSetSchemas(results);
const resultSetNames = resultSetSchemas.map(schema => schema.name);
const chunk = await this.cliServer.bqrsDecode(
results.query.resultsPaths.resultsPath,
schema.name,
RAW_RESULTS_PAGE_SIZE,
schema.pagination?.offsets[0]
);
const adaptedSchema = adaptSchema(schema);
const resultSet = adaptBqrs(adaptedSchema, chunk);
return {
t: 'ExtensionParsed',
pageNumber: 0,
numPages: numPagesOfResultSet(resultSet),
numInterpretedPages: numInterpretedPages(this._interpretation),
resultSet: { t: 'RawResultSet', ...resultSet },
selectedTable: undefined,
resultSetNames,
};
} else {
return { t: 'WebviewParsed' };
}
// This may not wind up being the page we actually show, if there are interpreted results,
// but speculatively send it anyway.
const selectedTable = getDefaultResultSetName(resultSetNames);
const schema = resultSetSchemas.find(
(resultSet) => resultSet.name == selectedTable
)!;
const chunk = await this.cliServer.bqrsDecode(
results.query.resultsPaths.resultsPath,
schema.name,
RAW_RESULTS_PAGE_SIZE,
schema.pagination?.offsets[0]
);
const adaptedSchema = adaptSchema(schema);
const resultSet = adaptBqrs(adaptedSchema, chunk);
return {
pageNumber: 0,
numPages: numPagesOfResultSet(resultSet),
numInterpretedPages: numInterpretedPages(this._interpretation),
resultSet: { t: 'RawResultSet', ...resultSet },
selectedTable: undefined,
resultSetNames,
};
};
await this.postMessage({
@@ -461,7 +453,6 @@ export class InterfaceManager extends DisposableObject {
const resultSet = adaptBqrs(adaptedSchema, chunk);
const parsedResultSets: ParsedResultSets = {
t: 'ExtensionParsed',
pageNumber,
resultSet: { t: 'RawResultSet', ...resultSet },
numPages: numPagesOfResultSet(resultSet),

View File

@@ -30,7 +30,7 @@ export class RawTable extends React.Component<RawTableProps, {}> {
const tableRows = dataRows.map((row: ResultRow, rowIndex: number) =>
<RawTableRow
key={rowIndex}
rowIndex={rowIndex}
rowIndex={rowIndex + this.props.offset}
row={row}
databaseUri={databaseUri}
/>

View File

@@ -15,7 +15,7 @@ import {
import { PathTable } from './alert-table';
import { RawTable } from './raw-results-table';
import { ResultTableProps, tableSelectionHeaderClassName, toggleDiagnosticsClassName, alertExtrasClassName } from './result-table-utils';
import { ParsedResultSets, ExtensionParsedResultSets } from '../adapt';
import { ParsedResultSets } from '../adapt';
import { vscode } from './vscode-api';
@@ -90,52 +90,23 @@ export class ResultTables
}
private getResultSetNames(resultSets: ResultSet[]): string[] {
if (this.props.parsedResultSets.t === 'ExtensionParsed') {
return this.props.parsedResultSets.resultSetNames.concat([ALERTS_TABLE_NAME]);
}
else {
return resultSets.map(resultSet => resultSet.schema.name);
}
}
/**
* Holds if we have a result set obtained from the extension that came
* from the ExtensionParsed branch of ParsedResultSets. This is evidence
* that the user has the experimental flag turned on that allows extension-side
* bqrs parsing.
*/
paginationAllowed(): boolean {
return this.props.parsedResultSets.t === 'ExtensionParsed';
return this.props.parsedResultSets.resultSetNames.concat([ALERTS_TABLE_NAME]);
}
constructor(props: ResultTablesProps) {
super(props);
const selectedTable = props.parsedResultSets.selectedTable || getDefaultResultSet(this.getResultSets());
let selectedPage: string;
switch (props.parsedResultSets.t) {
case 'ExtensionParsed':
selectedPage = (props.parsedResultSets.pageNumber + 1) + '';
break;
case 'WebviewParsed':
selectedPage = '';
break;
}
const selectedPage = (props.parsedResultSets.pageNumber + 1) + '';
this.state = { selectedTable, selectedPage };
}
private onTableSelectionChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
const selectedTable = event.target.value;
if (this.paginationAllowed()) {
vscode.postMessage({
t: 'changePage',
pageNumber: 0,
selectedTable
});
}
else
this.setState({ selectedTable });
vscode.postMessage({
t: 'changePage',
pageNumber: 0,
selectedTable
});
}
private alertTableExtras(): JSX.Element | undefined {
@@ -164,15 +135,11 @@ export class ResultTables
getOffset(): number {
const { parsedResultSets } = this.props;
switch (parsedResultSets.t) {
case 'ExtensionParsed':
return parsedResultSets.pageNumber * RAW_RESULTS_PAGE_SIZE;
case 'WebviewParsed':
return 0;
}
return parsedResultSets.pageNumber * RAW_RESULTS_PAGE_SIZE;
}
renderPageButtons(resultSets: ExtensionParsedResultSets): JSX.Element {
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
@@ -181,7 +148,7 @@ export class ResultTables
// not interpreted pages, because the extension doesn't know the view will default to showing alerts
// instead.
const numPages = selectedTable == ALERTS_TABLE_NAME ?
resultSets.numInterpretedPages : resultSets.numPages;
parsedResultSets.numInterpretedPages : parsedResultSets.numPages;
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ selectedPage: e.target.value });
@@ -201,14 +168,14 @@ export class ResultTables
const prevPage = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
vscode.postMessage({
t: 'changePage',
pageNumber: Math.max(resultSets.pageNumber - 1, 0),
pageNumber: Math.max(parsedResultSets.pageNumber - 1, 0),
selectedTable,
});
};
const nextPage = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
vscode.postMessage({
t: 'changePage',
pageNumber: Math.min(resultSets.pageNumber + 1, numPages - 1),
pageNumber: Math.min(parsedResultSets.pageNumber + 1, numPages - 1),
selectedTable,
});
};
@@ -230,13 +197,6 @@ export class ResultTables
</span>;
}
renderButtons(): JSX.Element {
if (this.props.parsedResultSets.t === 'ExtensionParsed' && this.paginationAllowed())
return this.renderPageButtons(this.props.parsedResultSets);
else
return <span />;
}
render(): React.ReactNode {
const { selectedTable } = this.state;
const resultSets = this.getResultSets();
@@ -250,7 +210,7 @@ export class ResultTables
resultSetNames.map(name => <option key={name} value={name}>{name}</option>);
return <div>
{this.renderButtons()}
{this.renderPageButtons()}
<div className={tableSelectionHeaderClassName}>
<select value={selectedTable} onChange={this.onTableSelectionChange}>
{resultSetOptions}

View File

@@ -1,12 +1,5 @@
import * as React from 'react';
import * as Rdom from 'react-dom';
import * as bqrs from 'semmle-bqrs';
import {
ElementBase,
PrimitiveColumnValue,
PrimitiveTypeKind,
tryGetResolvableLocation,
} from 'semmle-bqrs';
import { assertNever } from '../helpers-pure';
import {
DatabaseInfo,
@@ -22,8 +15,6 @@ import {
import { EventHandlers as EventHandlerList } from './event-handler-list';
import { ResultTables } from './result-tables';
import {
ResultValue,
ResultRow,
ParsedResultSets,
} from '../adapt';
import { ResultSet } from '../interface-types';
@@ -36,91 +27,6 @@ import { vscode } from './vscode-api';
* Displaying query results.
*/
async function* getChunkIterator(
response: Response
): AsyncIterableIterator<Uint8Array> {
if (!response.ok) {
throw new Error(
`Failed to load results: (${response.status}) ${response.statusText}`
);
}
const reader = response.body!.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) {
return;
}
yield value!;
}
}
function translatePrimitiveValue(
value: PrimitiveColumnValue,
type: PrimitiveTypeKind
): ResultValue {
switch (type) {
case 'i':
case 'f':
case 's':
case 'd':
case 'b':
return value.toString();
case 'u':
return {
uri: value as string,
};
}
}
async function parseResultSets(
response: Response
): Promise<readonly ResultSet[]> {
const chunks = getChunkIterator(response);
const resultSets: ResultSet[] = [];
await bqrs.parse(chunks, (resultSetSchema) => {
const columnTypes = resultSetSchema.columns.map((column) => column.type);
const rows: ResultRow[] = [];
resultSets.push({
t: 'RawResultSet',
schema: resultSetSchema,
rows: rows,
});
return (tuple) => {
const row: ResultValue[] = [];
tuple.forEach((value, index) => {
const type = columnTypes[index];
if (type.type === 'e') {
const element: ElementBase = value as ElementBase;
const label =
element.label !== undefined ? element.label : element.id.toString(); //REVIEW: URLs?
const resolvableLocation = tryGetResolvableLocation(element.location);
if (resolvableLocation !== undefined) {
row.push({
label: label,
location: resolvableLocation,
});
} else {
// No location link.
row.push(label);
}
} else {
row.push(
translatePrimitiveValue(value as PrimitiveColumnValue, type.type)
);
}
});
rows.push(row);
};
});
return resultSets;
}
interface ResultsInfo {
parsedResultSets: ParsedResultSets;
resultsPath: string;
@@ -200,7 +106,6 @@ class App extends React.Component<{}, ResultsViewState> {
this.updateStateWithNewResultsInfo({
resultsPath: '', // FIXME: Not used for interpreted, refactor so this is not needed
parsedResultSets: {
t: 'ExtensionParsed',
numPages: msg.numPages,
numInterpretedPages: msg.numPages,
resultSetNames: msg.resultSetNames,
@@ -269,13 +174,7 @@ class App extends React.Component<{}, ResultsViewState> {
resultsInfo: ResultsInfo
): Promise<readonly ResultSet[]> {
const parsedResultSets = resultsInfo.parsedResultSets;
switch (parsedResultSets.t) {
case 'WebviewParsed':
return await this.fetchResultSets(resultsInfo);
case 'ExtensionParsed': {
return [{ t: 'RawResultSet', ...parsedResultSets.resultSet }];
}
}
return [{ t: 'RawResultSet', ...parsedResultSets.resultSet }];
}
private async loadResults(): Promise<void> {
@@ -321,35 +220,6 @@ class App extends React.Component<{}, ResultsViewState> {
});
}
/**
* This is deprecated, because it calls `fetch`. We are moving
* towards doing all bqrs parsing in the extension.
*/
private async fetchResultSets(
resultsInfo: ResultsInfo
): Promise<readonly ResultSet[]> {
const unsortedResponse = await fetch(resultsInfo.resultsPath);
const unsortedResultSets = await parseResultSets(unsortedResponse);
return Promise.all(
unsortedResultSets.map(async (unsortedResultSet) => {
const sortedResultSetInfo = resultsInfo.sortedResultsMap.get(
unsortedResultSet.schema.name
);
if (sortedResultSetInfo === undefined) {
return unsortedResultSet;
}
const response = await fetch(sortedResultSetInfo.resultsPath);
const resultSets = await parseResultSets(response);
if (resultSets.length != 1) {
throw new Error(
`Expected sorted BQRS to contain a single result set, encountered ${resultSets.length} result sets.`
);
}
return resultSets[0];
})
);
}
private getSortStates(
resultsInfo: ResultsInfo
): Map<string, RawResultsSortState> {
@@ -369,7 +239,7 @@ class App extends React.Component<{}, ResultsViewState> {
displayedResults.resultsInfo !== null
) {
const parsedResultSets = displayedResults.resultsInfo.parsedResultSets;
const key = (parsedResultSets.t === 'ExtensionParsed' ? (parsedResultSets.selectedTable || '') + parsedResultSets.pageNumber : '');
const key = (parsedResultSets.selectedTable || '') + parsedResultSets.pageNumber;
return (
<ResultTables
key={key}