Add descriptive text and a link at top of query results

The descriptive text is the same as the label in the query history view.
The link opens the file that ran the query.
This commit is contained in:
Andrew Eisenberg
2020-12-16 10:22:05 -08:00
parent ca9510c08d
commit 3ea3eda8aa
7 changed files with 139 additions and 60 deletions

View File

@@ -10,6 +10,7 @@
- Add clearer error message when trying to run a command from the query history view if no item in the history is selected. [#702](https://github.com/github/vscode-codeql/pull/702)
- Fix a bug where it is not possible to download some database archives. This fix specifically addresses large archives and archives whose central directories do not align with file headers. [#700](https://github.com/github/vscode-codeql/pull/700)
- Avoid error dialogs when QL test discovery or database cleanup encounters a missing directory. [#706](https://github.com/github/vscode-codeql/pull/706)
- Add descriptive text and a link in the results view. [#711](https://github.com/github/vscode-codeql/pull/711)
## 1.3.7 - 24 November 2020

View File

@@ -286,6 +286,9 @@ export class InterfaceManager extends DisposableObject {
);
}
break;
case 'openFile':
await this.openFile(msg.filePath);
break;
default:
assertNever(msg);
}
@@ -413,6 +416,8 @@ export class InterfaceManager extends DisposableObject {
database: results.database,
shouldKeepOldResultsWhileRendering,
metadata: results.query.metadata,
queryName: results.toString(),
queryPath: results.query.program.queryPath
});
}
@@ -444,6 +449,8 @@ export class InterfaceManager extends DisposableObject {
resultSetNames,
pageSize: PAGE_SIZE.getValue(),
numPages: numInterpretedPages(this._interpretation),
queryName: this._displayedQuery.toString(),
queryPath: this._displayedQuery.query.program.queryPath
});
}
@@ -456,6 +463,11 @@ export class InterfaceManager extends DisposableObject {
return schemas['result-sets'];
}
public async openFile(filePath: string) {
const textDocument = await vscode.workspace.openTextDocument(filePath);
await vscode.window.showTextDocument(textDocument, vscode.ViewColumn.One);
}
/**
* Show a page of raw results from the chosen table.
*/
@@ -517,6 +529,8 @@ export class InterfaceManager extends DisposableObject {
database: results.database,
shouldKeepOldResultsWhileRendering: false,
metadata: results.query.metadata,
queryName: results.toString(),
queryPath: results.query.program.queryPath
});
}
@@ -534,8 +548,9 @@ export class InterfaceManager extends DisposableObject {
sourceInfo
);
sarif.runs.forEach(run => {
if (run.results !== undefined)
if (run.results !== undefined) {
sortInterpretedResults(run.results, sortState);
}
});
const numTotalResults = (() => {

View File

@@ -88,6 +88,8 @@ export interface SetStateMsg {
interpretation: undefined | Interpretation;
database: DatabaseInfo;
metadata?: QueryMetadata;
queryName: string;
queryPath: string;
/**
* Whether to keep displaying the old results while rendering the new results.
*
@@ -116,6 +118,8 @@ export interface ShowInterpretedPageMsg {
numPages: number;
pageSize: number;
resultSetNames: string[];
queryName: string;
queryPath: string;
}
/** Advance to the next or previous path no in the path viewer */
@@ -153,7 +157,8 @@ export type FromResultsViewMsg =
| ChangeRawResultsSortMsg
| ChangeInterpretedResultsSortMsg
| ResultViewLoaded
| ChangePage;
| ChangePage
| OpenFileMsg;
/**
* Message from the results view to open a database source
@@ -165,6 +170,14 @@ export interface ViewSourceFileMsg {
databaseUri: string;
}
/**
* Message from the results view to open a file in an editor.
*/
export interface OpenFileMsg {
t: 'openFile';
/* Full path to the file to open. */
filePath: string;
}
/**
* Message from the results view to toggle the display of

View File

@@ -28,7 +28,8 @@ export interface ResultTableProps {
}
export const className = 'vscode-codeql__result-table';
export const tableSelectionHeaderClassName = 'vscode-codeql__table-selection-header';
export const tableHeaderClassName = 'vscode-codeql__table-selection-header';
export const tableHeaderItemClassName = 'vscode-codeql__table-selection-header-item';
export const alertExtrasClassName = `${className}-alert-extras`;
export const toggleDiagnosticsClassName = `${className}-toggle-diagnostics`;
export const evenRowClassName = 'vscode-codeql__result-table-row--even';
@@ -45,7 +46,9 @@ export function jumpToLocationHandler(
jumpToLocation(loc, databaseUri);
e.preventDefault();
e.stopPropagation();
if (callback) callback();
if (callback) {
callback();
}
};
}
@@ -57,6 +60,13 @@ export function jumpToLocation(loc: ResolvableLocationValue, databaseUri: string
});
}
export function openFile(filePath: string): void {
vscode.postMessage({
t: 'openFile',
filePath
});
}
/**
* Render a location as a link which when clicked displays the original location.
*/

View File

@@ -11,19 +11,23 @@ import {
SELECT_TABLE_NAME,
getDefaultResultSetName,
ParsedResultSets,
IntoResultsViewMsg
IntoResultsViewMsg,
} from '../pure/interface-types';
import { PathTable } from './alert-table';
import { RawTable } from './raw-results-table';
import {
ResultTableProps,
tableSelectionHeaderClassName,
tableHeaderClassName,
tableHeaderItemClassName,
toggleDiagnosticsClassName,
alertExtrasClassName
alertExtrasClassName,
openFile
} from './result-table-utils';
import { vscode } from './vscode-api';
const FILE_PATH_REGEX = /^(?:.+[\\/])*(.+)$/;
/**
* Properties for the `ResultTables` component.
*/
@@ -38,6 +42,8 @@ export interface ResultTablesProps {
sortStates: Map<string, RawResultsSortState>;
interpretedSortState?: InterpretedResultsSortState;
isLoadingNewResults: boolean;
queryName: string;
queryPath: string;
}
/**
@@ -62,7 +68,7 @@ function getResultCount(resultSet: ResultSet): number {
function renderResultCountString(resultSet: ResultSet): JSX.Element {
const resultCount = getResultCount(resultSet);
return <span className="number-of-results">
return <span className={tableHeaderItemClassName}>
{resultCount} {resultCount === 1 ? 'result' : 'results'}
</span>;
}
@@ -213,28 +219,44 @@ export class ResultTables
});
};
return <span className="vscode-codeql__table-selection-header">
<button onClick={prevPage} >&#xab;</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}
const openQuery = () => {
openFile(this.props.queryPath);
};
const fileName = FILE_PATH_REGEX.exec(this.props.queryPath)?.[1] || 'query';
return (
<span className="vscode-codeql__table-selection-pagination">
<button onClick={prevPage} >&#xab;</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>
/&nbsp;{numPages}
</span>
<button value=">" onClick={nextPage} >&#xbb;</button>
<div className={tableHeaderItemClassName}>
{this.props.queryName}
</div>
<div className={tableHeaderItemClassName}>
<a
href="#"
onClick={openQuery}
className="vscode-codeql__result-table-location-link"
>Open {fileName}</a>
</div>
</span>
<button value=">" onClick={nextPage} >&#xbb;</button>
</span>;
);
}
render(): React.ReactNode {
@@ -248,32 +270,35 @@ export class ResultTables
const resultSetOptions =
resultSetNames.map(name => <option key={name} value={name}>{name}</option>);
return <div>
{this.renderPageButtons()}
<div className={tableSelectionHeaderClassName}>
<select value={selectedTable} onChange={this.onTableSelectionChange}>
{resultSetOptions}
</select>
{numberOfResults}
{selectedTable === ALERTS_TABLE_NAME ? this.alertTableExtras() : undefined}
return (
<div>
{this.renderPageButtons()}
<div className={tableHeaderClassName}>
</div>
<div className={tableHeaderClassName}>
<select value={selectedTable} onChange={this.onTableSelectionChange}>
{resultSetOptions}
</select>
{numberOfResults}
{selectedTable === ALERTS_TABLE_NAME ? this.alertTableExtras() : undefined}
{
this.props.isLoadingNewResults ?
<span className={UPDATING_RESULTS_TEXT_CLASS_NAME}>Updating results</span>
: null
}
</div>
{
this.props.isLoadingNewResults ?
<span className={UPDATING_RESULTS_TEXT_CLASS_NAME}>Updating results</span>
: null
resultSet &&
<ResultTable key={resultSet.schema.name} resultSet={resultSet}
databaseUri={this.props.database.databaseUri}
resultsPath={this.props.resultsPath}
sortState={this.props.sortStates.get(resultSet.schema.name)}
nonemptyRawResults={nonemptyRawResults}
showRawResults={() => { this.setState({ selectedTable: SELECT_TABLE_NAME }); }}
offset={this.getOffset()} />
}
</div>
{
resultSet &&
<ResultTable key={resultSet.schema.name} resultSet={resultSet}
databaseUri={this.props.database.databaseUri}
resultsPath={this.props.resultsPath}
sortState={this.props.sortStates.get(resultSet.schema.name)}
nonemptyRawResults={nonemptyRawResults}
showRawResults={() => { this.setState({ selectedTable: SELECT_TABLE_NAME }); }}
offset={this.getOffset()} />
}
</div>;
);
}
handleMessage(msg: IntoResultsViewMsg): void {

View File

@@ -11,7 +11,7 @@ import {
QueryMetadata,
ResultsPaths,
ALERTS_TABLE_NAME,
ParsedResultSets
ParsedResultSets,
} from '../pure/interface-types';
import { EventHandlers as EventHandlerList } from './event-handler-list';
import { ResultTables } from './result-tables';
@@ -37,6 +37,8 @@ interface ResultsInfo {
*/
shouldKeepOldResultsWhileRendering: boolean;
metadata?: QueryMetadata;
queryName: string;
queryPath: string;
}
interface Results {
@@ -96,6 +98,8 @@ class App extends React.Component<{}, ResultsViewState> {
shouldKeepOldResultsWhileRendering:
msg.shouldKeepOldResultsWhileRendering,
metadata: msg.metadata,
queryName: msg.queryName,
queryPath: msg.queryPath,
});
this.loadResults();
@@ -127,6 +131,8 @@ class App extends React.Component<{}, ResultsViewState> {
interpretation: msg.interpretation,
shouldKeepOldResultsWhileRendering: true,
metadata: msg.metadata,
queryName: msg.queryName,
queryPath: msg.queryPath,
});
this.loadResults();
break;
@@ -280,6 +286,8 @@ class App extends React.Component<{}, ResultsViewState> {
this.state.isExpectingResultsUpdate ||
this.state.nextResultsInfo !== null
}
queryName={displayedResults.resultsInfo.queryName}
queryPath={displayedResults.resultsInfo.queryPath}
/>
);
} else {

View File

@@ -8,13 +8,24 @@
display: flex;
padding: 0.5em 0;
align-items: center;
justify-content: space-between;
}
.vscode-codeql__table-selection-pagination {
display: flex;
padding: 0.5em 0;
align-items: center;
}
.vscode-codeql__table-selection-header-item {
padding-left: 2em;
}
.vscode-codeql__table-selection-header select {
border: 0;
}
.vscode-codeql__table-selection-header button {
.vscode-codeql__table-selection-pagination button {
padding: 0.3rem;
margin: 0.2rem;
border: 0;
@@ -25,11 +36,11 @@
opacity: 0.8;
}
.vscode-codeql__table-selection-header button:hover {
.vscode-codeql__table-selection-pagination button:hover {
opacity: 1;
}
.vscode-codeql__table-selection-header input[type="number"] {
.vscode-codeql__table-selection-pagination input[type="number"] {
border-radius: 0;
padding: 0.3rem;
margin: 0.2rem;
@@ -185,10 +196,6 @@ td.vscode-codeql__path-index-cell {
opacity: 0.6;
}
.number-of-results {
padding-left: 3em;
}
.vscode-codeql__compare-header {
display: flex;
}