Display error message when can't compare queries

* The error message will be displayed instead of the empty results
  tables.
* Also, uncomment onEnterRules. That should never have been committed.
* Also, extract CompareTable to its own component.
This commit is contained in:
Andrew Eisenberg 2020-06-16 11:06:42 -07:00
Родитель 15d65b308c
Коммит 75fe8fb040
8 изменённых файлов: 164 добавлений и 121 удалений

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

@ -66,6 +66,14 @@ export class CompareInterfaceManager extends DisposableObject {
selectedResultSetName selectedResultSetName
); );
if (currentResultSetName) { if (currentResultSetName) {
let rows: QueryCompareResult | undefined;
let message: string | undefined;
try {
rows = this.compareResults(fromResultSet, toResultSet);
} catch (e) {
message = e.message;
}
await this.postMessage({ await this.postMessage({
t: 'setComparisons', t: 'setComparisons',
stats: { stats: {
@ -81,7 +89,7 @@ export class CompareInterfaceManager extends DisposableObject {
}, },
toQuery: { toQuery: {
name: to.options.label name: to.options.label
? to.interpolate(from.getLabel()) ? to.interpolate(to.getLabel())
: to.queryName, : to.queryName,
status: to.statusString, status: to.statusString,
time: to.time, time: to.time,
@ -90,7 +98,8 @@ export class CompareInterfaceManager extends DisposableObject {
columns: fromResultSet.schema.columns, columns: fromResultSet.schema.columns,
commonResultSetNames, commonResultSetNames,
currentResultSetName: currentResultSetName, currentResultSetName: currentResultSetName,
rows: this.compareResults(fromResultSet, toResultSet), rows,
message,
datebaseUri: to.database.databaseUri, datebaseUri: to.database.databaseUri,
}); });
} }

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

@ -2,35 +2,33 @@ import * as React from 'react';
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import * as Rdom from 'react-dom'; import * as Rdom from 'react-dom';
import RawTableHeader from '../../view/RawTableHeader';
import { import {
ToCompareViewMessage, ToCompareViewMessage,
SetComparisonsMessage, SetComparisonsMessage,
} from '../../interface-types'; } from '../../interface-types';
import CompareSelector from './CompareSelector'; import CompareSelector from './CompareSelector';
import { vscode } from '../../view/vscode-api'; import { vscode } from '../../view/vscode-api';
import RawTableRow from '../../view/RawTableRow'; import CompareTable from './CompareTable';
import { ResultRow } from '../../adapt';
import { className } from '../../view/result-table-utils';
const emptyComparison: SetComparisonsMessage = { const emptyComparison: SetComparisonsMessage = {
t: 'setComparisons', t: 'setComparisons',
stats: {}, stats: {},
rows: { rows: undefined,
from: [],
to: [],
},
columns: [], columns: [],
commonResultSetNames: [], commonResultSetNames: [],
currentResultSetName: '', currentResultSetName: '',
datebaseUri: '', datebaseUri: '',
message: 'Empty comparison'
}; };
export function Compare(props: {}): JSX.Element { export function Compare(_: {}): JSX.Element {
const [comparison, setComparison] = useState<SetComparisonsMessage>( const [comparison, setComparison] = useState<SetComparisonsMessage>(
emptyComparison emptyComparison
); );
const message = comparison.message || 'Empty comparison';
const hasRows = comparison.rows && (comparison.rows.to.length || comparison.rows.from.length);
useEffect(() => { useEffect(() => {
window.addEventListener('message', (evt: MessageEvent) => { window.addEventListener('message', (evt: MessageEvent) => {
const msg: ToCompareViewMessage = evt.data; const msg: ToCompareViewMessage = evt.data;
@ -48,7 +46,9 @@ export function Compare(props: {}): JSX.Element {
return ( return (
<> <>
<div className="vscode-codeql__compare-header"> <div className="vscode-codeql__compare-header">
<div>Table to compare:</div> <div className="vscode-codeql__compare-header-item">
Table to compare:
</div>
<CompareSelector <CompareSelector
availableResultSets={comparison.commonResultSetNames} availableResultSets={comparison.commonResultSetNames}
currentResultSetName={comparison.currentResultSetName} currentResultSetName={comparison.currentResultSetName}
@ -57,60 +57,11 @@ export function Compare(props: {}): JSX.Element {
} }
/> />
</div> </div>
<table className="vscode-codeql__compare-body"> {hasRows ? (
<thead> <CompareTable comparison={comparison}></CompareTable>
<tr> ) : (
<td> <div className="vscode-codeql__compare-message">{message}</div>
<a )}
onClick={() => openQuery('from')}
className="vscode-codeql__compare-open"
>
{comparison.stats.fromQuery?.name}
</a>
</td>
<td>
<a
onClick={() => openQuery('to')}
className="vscode-codeql__compare-open"
>
{comparison.stats.toQuery?.name}
</a>
</td>
</tr>
<tr>
<td>{comparison.stats.fromQuery?.time}</td>
<td>{comparison.stats.toQuery?.time}</td>
</tr>
<tr>
<th>{comparison.rows.from.length} rows removed</th>
<th>{comparison.rows.to.length} rows added</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<table className={className}>
<RawTableHeader
columns={comparison.columns}
schemaName={comparison.currentResultSetName}
preventSort={true}
/>
{createRows(comparison.rows.from, comparison.datebaseUri)}
</table>
</td>
<td>
<table className={className}>
<RawTableHeader
columns={comparison.columns}
schemaName={comparison.currentResultSetName}
preventSort={true}
/>
{createRows(comparison.rows.to, comparison.datebaseUri)}
</table>
</td>
</tr>
</tbody>
</table>
</> </>
); );
} catch (err) { } catch (err) {
@ -119,28 +70,6 @@ export function Compare(props: {}): JSX.Element {
} }
} }
async function openQuery(kind: 'from' | 'to') {
vscode.postMessage({
t: 'openQuery',
kind,
});
}
function createRows(rows: ResultRow[], databaseUri: string) {
return (
<tbody>
{rows.map((row, rowIndex) => (
<RawTableRow
key={rowIndex}
rowIndex={rowIndex}
row={row}
databaseUri={databaseUri}
/>
))}
</tbody>
);
}
Rdom.render( Rdom.render(
<Compare />, <Compare />,
document.getElementById('root'), document.getElementById('root'),

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

@ -0,0 +1,96 @@
import * as React from 'react';
import { SetComparisonsMessage } from '../../interface-types';
import RawTableHeader from '../../view/RawTableHeader';
import { className } from '../../view/result-table-utils';
import { ResultRow } from '../../adapt';
import RawTableRow from '../../view/RawTableRow';
import { vscode } from '../../view/vscode-api';
interface Props {
comparison: SetComparisonsMessage;
}
export default function CompareTable(props: Props) {
const comparison = props.comparison;
const rows = props.comparison.rows!;
async function openQuery(kind: 'from' | 'to') {
vscode.postMessage({
t: 'openQuery',
kind,
});
}
function createRows(rows: ResultRow[], databaseUri: string) {
return (
<tbody>
{rows.map((row, rowIndex) => (
<RawTableRow
key={rowIndex}
rowIndex={rowIndex}
row={row}
databaseUri={databaseUri}
/>
))}
</tbody>
);
}
return (
<table className='vscode-codeql__compare-body'>
<thead>
<tr>
<td>
<a
onClick={() => openQuery('from')}
className='vscode-codeql__compare-open'
>
{comparison.stats.fromQuery?.name}
</a>
</td>
<td>
<a
onClick={() => openQuery('to')}
className='vscode-codeql__compare-open'
>
{comparison.stats.toQuery?.name}
</a>
</td>
</tr>
<tr>
<td>{comparison.stats.fromQuery?.time}</td>
<td>{comparison.stats.toQuery?.time}</td>
</tr>
<tr>
<th>{rows.from.length} rows removed</th>
<th>{rows.to.length} rows added</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<table className={className}>
<RawTableHeader
columns={comparison.columns}
schemaName={comparison.currentResultSetName}
preventSort={true}
/>
{createRows(rows.from, comparison.datebaseUri)}
</table>
</td>
<td>
<table className={className}>
<RawTableHeader
columns={comparison.columns}
schemaName={comparison.currentResultSetName}
preventSort={true}
/>
{createRows(rows.to, comparison.datebaseUri)}
</table>
</td>
</tr>
</tbody>
</table>
);
}

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

@ -234,7 +234,8 @@ export interface SetComparisonsMessage {
readonly columns: readonly ColumnSchema[]; readonly columns: readonly ColumnSchema[];
readonly commonResultSetNames: string[]; readonly commonResultSetNames: string[];
readonly currentResultSetName: string; readonly currentResultSetName: string;
readonly rows: QueryCompareResult; readonly rows: QueryCompareResult | undefined;
readonly message: string | undefined;
readonly datebaseUri: string; readonly datebaseUri: string;
} }

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

@ -1,5 +1,4 @@
import { languages } from 'vscode'; import { languages, IndentAction, OnEnterRule } from 'vscode';
/** /**
* OnEnterRules are available in language-configurations, but you cannot specify them in the language-configuration.json. * OnEnterRules are available in language-configurations, but you cannot specify them in the language-configuration.json.
@ -27,29 +26,32 @@ export function install() {
languages.setLanguageConfiguration('dbscheme', langConfig); languages.setLanguageConfiguration('dbscheme', langConfig);
} }
const onEnterRules: string[] = [ const onEnterRules: OnEnterRule[] = [
// { {
// // e.g. /** | */ // e.g. /** | */
// beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/, beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
// afterText: /^\s*\*\/$/, afterText: /^\s*\*\/$/,
// action: { indentAction: IndentAction.IndentOutdent, appendText: ' * ' } action: { indentAction: IndentAction.IndentOutdent, appendText: ' * ' },
// }, { },
// // e.g. /** ...| {
// beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/, // e.g. /** ...|
// action: { indentAction: IndentAction.None, appendText: ' * ' } beforeText: /^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,
// }, { action: { indentAction: IndentAction.None, appendText: ' * ' },
// // e.g. * ...| },
// beforeText: /^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/, {
// oneLineAboveText: /^(\s*(\/\*\*|\*)).*/, // e.g. * ...|
// action: { indentAction: IndentAction.None, appendText: '* ' } beforeText: /^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/,
// }, { // oneLineAboveText: /^(\s*(\/\*\*|\*)).*/,
// // e.g. */| action: { indentAction: IndentAction.None, appendText: '* ' },
// beforeText: /^(\t|[ ])*[ ]\*\/\s*$/, },
// action: { indentAction: IndentAction.None, removeText: 1 } {
// }, // e.g. */|
// { beforeText: /^(\t|[ ])*[ ]\*\/\s*$/,
// // e.g. *-----*/| action: { indentAction: IndentAction.None, removeText: 1 },
// beforeText: /^(\t|[ ])*[ ]\*[^/]*\*\/\s*$/, },
// action: { indentAction: IndentAction.None, removeText: 1 } {
// } // e.g. *-----*/|
beforeText: /^(\t|[ ])*[ ]\*[^/]*\*\/\s*$/,
action: { indentAction: IndentAction.None, removeText: 1 },
},
]; ];

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

@ -159,7 +159,6 @@ const DOUBLE_CLICK_TIME = 500;
export class QueryHistoryManager { export class QueryHistoryManager {
treeDataProvider: HistoryTreeDataProvider; treeDataProvider: HistoryTreeDataProvider;
ctx: ExtensionContext;
treeView: vscode.TreeView<CompletedQuery>; treeView: vscode.TreeView<CompletedQuery>;
lastItemClick: { time: Date; item: CompletedQuery } | undefined; lastItemClick: { time: Date; item: CompletedQuery } | undefined;
@ -304,7 +303,6 @@ export class QueryHistoryManager {
private selectedCallback: (item: CompletedQuery) => Promise<void>, private selectedCallback: (item: CompletedQuery) => Promise<void>,
private doCompareCallback: (from: CompletedQuery, to: CompletedQuery) => Promise<void>, private doCompareCallback: (from: CompletedQuery, to: CompletedQuery) => Promise<void>,
) { ) {
this.ctx = ctx;
const treeDataProvider = this.treeDataProvider = new HistoryTreeDataProvider(ctx); const treeDataProvider = this.treeDataProvider = new HistoryTreeDataProvider(ctx);
this.treeView = Window.createTreeView('codeQLQueryHistory', { treeDataProvider }); this.treeView = Window.createTreeView('codeQLQueryHistory', { treeDataProvider });
// Lazily update the tree view selection due to limitations of TreeView API (see // Lazily update the tree view selection due to limitations of TreeView API (see

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

@ -72,7 +72,9 @@ export class ResultTables
private getResultSets(): ResultSet[] { private getResultSets(): ResultSet[] {
const resultSets: ResultSet[] = const resultSets: ResultSet[] =
this.props.rawResultSets.map(rs => ({ t: 'RawResultSet', ...rs })); // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore 2783
this.props.rawResultSets.map((rs) => ({ t: 'RawResultSet', ...rs }));
if (this.props.interpretation != undefined) { if (this.props.interpretation != undefined) {
resultSets.push({ resultSets.push({
@ -286,8 +288,6 @@ class ResultTable extends React.Component<ResultTableProps, {}> {
{...this.props} resultSet={resultSet} />; {...this.props} resultSet={resultSet} />;
case 'SarifResultSet': return <PathTable case 'SarifResultSet': return <PathTable
{...this.props} resultSet={resultSet} />; {...this.props} resultSet={resultSet} />;
default:
throw new Error('Invalid type');
} }
} }
} }

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

@ -161,6 +161,14 @@ td.vscode-codeql__path-index-cell {
display: flex; display: flex;
} }
.vscode-codeql__compare-header-item {
margin: 0 1.5rem;
}
.vscode-codeql__compare-message {
padding: 1.5rem;
}
.vscode-codeql__compare-body { .vscode-codeql__compare-body {
margin: 20px 0; margin: 20px 0;
width: 100%; width: 100%;