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:
@@ -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
|
||||
|
||||
|
||||
@@ -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 = (() => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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} >«</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} >«</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}>
|
||||
<a
|
||||
href="#"
|
||||
onClick={openQuery}
|
||||
className="vscode-codeql__result-table-location-link"
|
||||
>Open {fileName}</a>
|
||||
</div>
|
||||
</span>
|
||||
<button value=">" onClick={nextPage} >»</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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user