Merge branch 'main' into robertbrignull/details-multiple-editors
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
## 1.8.10 - 15 August 2023
|
||||
|
||||
- Add a code lens to make the `CodeQL: Open Referenced File` command more discoverable. Click the "Open referenced file" prompt in a `.qlref` file to jump to the referenced `.ql` file. [#2704](https://github.com/github/vscode-codeql/pull/2704)
|
||||
|
||||
## 1.8.9 - 3 August 2023
|
||||
|
||||
- Remove "last updated" information and sorting from variant analysis results view. [#2637](https://github.com/github/vscode-codeql/pull/2637)
|
||||
|
||||
6
extensions/ql-vscode/package-lock.json
generated
6
extensions/ql-vscode/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "vscode-codeql",
|
||||
"version": "1.8.10",
|
||||
"version": "1.8.11",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "vscode-codeql",
|
||||
"version": "1.8.10",
|
||||
"version": "1.8.11",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -147,7 +147,7 @@
|
||||
"webpack-cli": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.13.0",
|
||||
"node": "^16.17.1",
|
||||
"npm": ">=7.20.6",
|
||||
"vscode": "^1.67.0"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "CodeQL for Visual Studio Code",
|
||||
"author": "GitHub",
|
||||
"private": true,
|
||||
"version": "1.8.10",
|
||||
"version": "1.8.11",
|
||||
"publisher": "GitHub",
|
||||
"license": "MIT",
|
||||
"icon": "media/VS-marketplace-CodeQL-icon.png",
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.67.0",
|
||||
"node": "^16.13.0",
|
||||
"node": "^16.17.1",
|
||||
"npm": ">=7.20.6"
|
||||
},
|
||||
"categories": [
|
||||
|
||||
@@ -34,16 +34,27 @@ export class ModelDetailsDataProvider
|
||||
return this.onDidChangeTreeDataEmitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the data displayed in the tree view.
|
||||
*
|
||||
* Will only trigger an update if the data has changed. This relies on
|
||||
* object identity, so be sure to not mutate the data passed to this
|
||||
* method and instead always pass new objects/arrays.
|
||||
*/
|
||||
public async setState(
|
||||
externalApiUsages: ExternalApiUsage[],
|
||||
databaseItem: DatabaseItem,
|
||||
): Promise<void> {
|
||||
this.externalApiUsages = externalApiUsages;
|
||||
this.databaseItem = databaseItem;
|
||||
this.sourceLocationPrefix = await this.databaseItem.getSourceLocationPrefix(
|
||||
this.cliServer,
|
||||
);
|
||||
this.onDidChangeTreeDataEmitter.fire();
|
||||
if (
|
||||
this.externalApiUsages !== externalApiUsages ||
|
||||
this.databaseItem !== databaseItem
|
||||
) {
|
||||
this.externalApiUsages = externalApiUsages;
|
||||
this.databaseItem = databaseItem;
|
||||
this.sourceLocationPrefix =
|
||||
await this.databaseItem.getSourceLocationPrefix(this.cliServer);
|
||||
this.onDidChangeTreeDataEmitter.fire();
|
||||
}
|
||||
}
|
||||
|
||||
getTreeItem(item: ModelDetailsTreeViewItem): TreeItem {
|
||||
|
||||
@@ -134,6 +134,7 @@ import { TestRunner } from "./query-testing/test-runner";
|
||||
import { TestManagerBase } from "./query-testing/test-manager-base";
|
||||
import { NewQueryRunner, QueryRunner, QueryServerClient } from "./query-server";
|
||||
import { QueriesModule } from "./queries-panel/queries-module";
|
||||
import { OpenReferencedFileCodeLensProvider } from "./local-queries/open-referenced-file-code-lens-provider";
|
||||
|
||||
/**
|
||||
* extension.ts
|
||||
@@ -332,10 +333,17 @@ export async function activate(
|
||||
|
||||
const app = new ExtensionApp(ctx);
|
||||
|
||||
const codelensProvider = new QuickEvalCodeLensProvider();
|
||||
const quickEvalCodeLensProvider = new QuickEvalCodeLensProvider();
|
||||
languages.registerCodeLensProvider(
|
||||
{ scheme: "file", language: "ql" },
|
||||
codelensProvider,
|
||||
quickEvalCodeLensProvider,
|
||||
);
|
||||
|
||||
const openReferencedFileCodeLensProvider =
|
||||
new OpenReferencedFileCodeLensProvider();
|
||||
languages.registerCodeLensProvider(
|
||||
{ scheme: "file", pattern: "**/*.qlref" },
|
||||
openReferencedFileCodeLensProvider,
|
||||
);
|
||||
|
||||
ctx.subscriptions.push(distributionConfigListener);
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
CancellationTokenSource,
|
||||
QuickPickItem,
|
||||
Range,
|
||||
TabInputText,
|
||||
Uri,
|
||||
window,
|
||||
} from "vscode";
|
||||
@@ -300,20 +301,18 @@ export class LocalQueries extends DisposableObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current active query.
|
||||
*
|
||||
* For now, the "active query" is just whatever query is in the active text editor. Once we have a
|
||||
* proper "queries" panel, we can provide a way to select the current query there.
|
||||
* Gets the current active query. This is the query that is open in the active tab.
|
||||
*/
|
||||
public async getCurrentQuery(allowLibraryFiles: boolean): Promise<string> {
|
||||
const editor = window.activeTextEditor;
|
||||
if (editor === undefined) {
|
||||
const input = window.tabGroups.activeTabGroup.activeTab?.input;
|
||||
|
||||
if (input === undefined || !isTabInputText(input)) {
|
||||
throw new Error(
|
||||
"No query was selected. Please select a query and try again.",
|
||||
);
|
||||
}
|
||||
|
||||
return validateQueryUri(editor.document.uri, allowLibraryFiles);
|
||||
return validateQueryUri(input.uri, allowLibraryFiles);
|
||||
}
|
||||
|
||||
private async createSkeletonQuery(): Promise<void> {
|
||||
@@ -581,3 +580,7 @@ export class LocalQueries extends DisposableObject {
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
function isTabInputText(input: any): input is TabInputText {
|
||||
return input?.uri !== undefined;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
CodeLensProvider,
|
||||
TextDocument,
|
||||
CodeLens,
|
||||
Command,
|
||||
Range,
|
||||
} from "vscode";
|
||||
|
||||
export class OpenReferencedFileCodeLensProvider implements CodeLensProvider {
|
||||
async provideCodeLenses(document: TextDocument): Promise<CodeLens[]> {
|
||||
const codeLenses: CodeLens[] = [];
|
||||
|
||||
// A .qlref file is a file that contains a single line with a path to a .ql file.
|
||||
if (document.fileName.endsWith(".qlref")) {
|
||||
const textLine = document.lineAt(0);
|
||||
const range: Range = new Range(
|
||||
textLine.range.start.line,
|
||||
textLine.range.start.character,
|
||||
textLine.range.start.line,
|
||||
textLine.range.end.character,
|
||||
);
|
||||
|
||||
const command: Command = {
|
||||
command: "codeQL.openReferencedFile",
|
||||
title: `Open referenced file`,
|
||||
arguments: [document.uri],
|
||||
};
|
||||
const codeLens = new CodeLens(range, command);
|
||||
codeLenses.push(codeLens);
|
||||
}
|
||||
|
||||
return codeLenses;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as React from "react";
|
||||
import { ALERTS_TABLE_NAME } from "../../common/interface-types";
|
||||
import {
|
||||
alertExtrasClassName,
|
||||
toggleDiagnosticsClassName,
|
||||
} from "./result-table-utils";
|
||||
|
||||
interface Props {
|
||||
selectedTable: string;
|
||||
problemsViewSelected: boolean;
|
||||
handleCheckboxChanged: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export function ProblemsViewCheckbox(props: Props): JSX.Element | null {
|
||||
const { selectedTable, problemsViewSelected, handleCheckboxChanged } = props;
|
||||
|
||||
if (selectedTable !== ALERTS_TABLE_NAME) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={alertExtrasClassName}>
|
||||
<div className={toggleDiagnosticsClassName}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="toggle-diagnostics"
|
||||
name="toggle-diagnostics"
|
||||
onChange={handleCheckboxChanged}
|
||||
checked={problemsViewSelected}
|
||||
/>
|
||||
<label htmlFor="toggle-diagnostics">
|
||||
Show results in Problems view
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
extensions/ql-vscode/src/view/results/ResultCount.tsx
Normal file
29
extensions/ql-vscode/src/view/results/ResultCount.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as React from "react";
|
||||
import { ResultSet } from "../../common/interface-types";
|
||||
import { tableHeaderItemClassName } from "./result-table-utils";
|
||||
|
||||
interface Props {
|
||||
resultSet?: ResultSet;
|
||||
}
|
||||
|
||||
function getResultCount(resultSet: ResultSet): number {
|
||||
switch (resultSet.t) {
|
||||
case "RawResultSet":
|
||||
return resultSet.schema.rows;
|
||||
case "InterpretedResultSet":
|
||||
return resultSet.interpretation.numTotalResults;
|
||||
}
|
||||
}
|
||||
|
||||
export function ResultCount(props: Props): JSX.Element | null {
|
||||
if (!props.resultSet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const resultCount = getResultCount(props.resultSet);
|
||||
return (
|
||||
<span className={tableHeaderItemClassName}>
|
||||
{resultCount} {resultCount === 1 ? "result" : "results"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -14,16 +14,14 @@ import {
|
||||
ParsedResultSets,
|
||||
IntoResultsViewMsg,
|
||||
} from "../../common/interface-types";
|
||||
import {
|
||||
tableHeaderClassName,
|
||||
tableHeaderItemClassName,
|
||||
toggleDiagnosticsClassName,
|
||||
alertExtrasClassName,
|
||||
} from "./result-table-utils";
|
||||
import { tableHeaderClassName } from "./result-table-utils";
|
||||
import { vscode } from "../vscode-api";
|
||||
import { sendTelemetry } from "../common/telemetry";
|
||||
import { ResultTable } from "./ResultTable";
|
||||
import { ResultTablesHeader } from "./ResultTablesHeader";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { ResultCount } from "./ResultCount";
|
||||
import { ProblemsViewCheckbox } from "./ProblemsViewCheckbox";
|
||||
|
||||
/**
|
||||
* Properties for the `ResultTables` component.
|
||||
@@ -43,35 +41,9 @@ interface ResultTablesProps {
|
||||
queryPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* State for the `ResultTables` component.
|
||||
*/
|
||||
interface ResultTablesState {
|
||||
selectedTable: string; // name of selected result set
|
||||
problemsViewSelected: boolean;
|
||||
}
|
||||
|
||||
const UPDATING_RESULTS_TEXT_CLASS_NAME =
|
||||
"vscode-codeql__result-tables-updating-text";
|
||||
|
||||
function getResultCount(resultSet: ResultSet): number {
|
||||
switch (resultSet.t) {
|
||||
case "RawResultSet":
|
||||
return resultSet.schema.rows;
|
||||
case "InterpretedResultSet":
|
||||
return resultSet.interpretation.numTotalResults;
|
||||
}
|
||||
}
|
||||
|
||||
function renderResultCountString(resultSet: ResultSet): JSX.Element {
|
||||
const resultCount = getResultCount(resultSet);
|
||||
return (
|
||||
<span className={tableHeaderItemClassName}>
|
||||
{resultCount} {resultCount === 1 ? "result" : "results"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function getInterpretedTableName(interpretation: Interpretation): string {
|
||||
return interpretation.data.t === "GraphInterpretationData"
|
||||
? GRAPH_TABLE_NAME
|
||||
@@ -122,72 +94,93 @@ function getResultSets(
|
||||
* Displays multiple `ResultTable` tables, where the table to be displayed is selected by a
|
||||
* dropdown.
|
||||
*/
|
||||
export class ResultTables extends React.Component<
|
||||
ResultTablesProps,
|
||||
ResultTablesState
|
||||
> {
|
||||
constructor(props: ResultTablesProps) {
|
||||
super(props);
|
||||
const selectedTable =
|
||||
props.parsedResultSets.selectedTable ||
|
||||
getDefaultResultSet(
|
||||
getResultSets(props.rawResultSets, props.interpretation),
|
||||
);
|
||||
this.state = {
|
||||
selectedTable,
|
||||
problemsViewSelected: false,
|
||||
};
|
||||
}
|
||||
export function ResultTables(props: ResultTablesProps) {
|
||||
const {
|
||||
parsedResultSets,
|
||||
rawResultSets,
|
||||
interpretation,
|
||||
database,
|
||||
resultsPath,
|
||||
metadata,
|
||||
origResultsPaths,
|
||||
isLoadingNewResults,
|
||||
sortStates,
|
||||
} = props;
|
||||
|
||||
componentDidUpdate(
|
||||
prevProps: Readonly<ResultTablesProps>,
|
||||
prevState: Readonly<ResultTablesState>,
|
||||
snapshot?: any,
|
||||
) {
|
||||
const [selectedTable, setSelectedTable] = useState(
|
||||
parsedResultSets.selectedTable ||
|
||||
getDefaultResultSet(getResultSets(rawResultSets, interpretation)),
|
||||
);
|
||||
const [problemsViewSelected, setProblemsViewSelected] = useState(false);
|
||||
|
||||
const handleMessage = useCallback((msg: IntoResultsViewMsg): void => {
|
||||
switch (msg.t) {
|
||||
case "untoggleShowProblems":
|
||||
setProblemsViewSelected(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
}, []);
|
||||
|
||||
const vscodeMessageHandler = useCallback(
|
||||
(evt: MessageEvent): void => {
|
||||
// sanitize origin
|
||||
const origin = evt.origin.replace(/\n|\r/g, "");
|
||||
evt.origin === window.origin
|
||||
? handleMessage(evt.data as IntoResultsViewMsg)
|
||||
: console.error(`Invalid event origin ${origin}`);
|
||||
},
|
||||
[handleMessage],
|
||||
);
|
||||
|
||||
// TODO: Duplicated from results.tsx consider a way to
|
||||
// avoid this duplication
|
||||
useEffect(() => {
|
||||
window.addEventListener("message", vscodeMessageHandler);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("message", vscodeMessageHandler);
|
||||
};
|
||||
}, [vscodeMessageHandler]);
|
||||
|
||||
useEffect(() => {
|
||||
const resultSetExists =
|
||||
this.props.parsedResultSets.resultSetNames.some(
|
||||
(v) => this.state.selectedTable === v,
|
||||
) ||
|
||||
getResultSets(this.props.rawResultSets, this.props.interpretation).some(
|
||||
(v) => this.state.selectedTable === v.schema.name,
|
||||
parsedResultSets.resultSetNames.some((v) => selectedTable === v) ||
|
||||
getResultSets(rawResultSets, interpretation).some(
|
||||
(v) => selectedTable === v.schema.name,
|
||||
);
|
||||
|
||||
// If the selected result set does not exist, select the default result set.
|
||||
if (!resultSetExists) {
|
||||
this.setState((state, props) => {
|
||||
const selectedTable =
|
||||
props.parsedResultSets.selectedTable ||
|
||||
getDefaultResultSet(
|
||||
getResultSets(props.rawResultSets, props.interpretation),
|
||||
);
|
||||
|
||||
return { selectedTable };
|
||||
});
|
||||
setSelectedTable(
|
||||
parsedResultSets.selectedTable ||
|
||||
getDefaultResultSet(getResultSets(rawResultSets, interpretation)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [parsedResultSets, interpretation, rawResultSets, selectedTable]);
|
||||
|
||||
private onTableSelectionChange = (
|
||||
event: React.ChangeEvent<HTMLSelectElement>,
|
||||
): void => {
|
||||
const selectedTable = event.target.value;
|
||||
vscode.postMessage({
|
||||
t: "changePage",
|
||||
pageNumber: 0,
|
||||
selectedTable,
|
||||
});
|
||||
sendTelemetry("local-results-table-selection");
|
||||
};
|
||||
const onTableSelectionChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLSelectElement>): void => {
|
||||
const selectedTable = event.target.value;
|
||||
vscode.postMessage({
|
||||
t: "changePage",
|
||||
pageNumber: 0,
|
||||
selectedTable,
|
||||
});
|
||||
sendTelemetry("local-results-table-selection");
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
private alertTableExtras(): JSX.Element | undefined {
|
||||
const { database, resultsPath, metadata, origResultsPaths } = this.props;
|
||||
const handleCheckboxChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.checked === this.state.problemsViewSelected) {
|
||||
const handleCheckboxChanged = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.checked === problemsViewSelected) {
|
||||
// no change
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
problemsViewSelected: e.target.checked,
|
||||
});
|
||||
setProblemsViewSelected(e.target.checked);
|
||||
if (e.target.checked) {
|
||||
sendTelemetry("local-results-show-results-in-problems-view");
|
||||
}
|
||||
@@ -200,133 +193,69 @@ export class ResultTables extends React.Component<
|
||||
metadata,
|
||||
});
|
||||
}
|
||||
};
|
||||
},
|
||||
[database, metadata, origResultsPaths, problemsViewSelected, resultsPath],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={alertExtrasClassName}>
|
||||
<div className={toggleDiagnosticsClassName}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="toggle-diagnostics"
|
||||
name="toggle-diagnostics"
|
||||
onChange={handleCheckboxChanged}
|
||||
checked={this.state.problemsViewSelected}
|
||||
/>
|
||||
<label htmlFor="toggle-diagnostics">
|
||||
Show results in Problems view
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const offset = parsedResultSets.pageNumber * parsedResultSets.pageSize;
|
||||
|
||||
getOffset(): number {
|
||||
const { parsedResultSets } = this.props;
|
||||
return parsedResultSets.pageNumber * parsedResultSets.pageSize;
|
||||
}
|
||||
const resultSets = useMemo(
|
||||
() => getResultSets(rawResultSets, interpretation),
|
||||
[interpretation, rawResultSets],
|
||||
);
|
||||
const resultSetNames = getResultSetNames(interpretation, parsedResultSets);
|
||||
|
||||
sendResultsPageChangedTelemetry() {
|
||||
sendTelemetry("local-results-alert-table-page-changed");
|
||||
}
|
||||
const resultSet = useMemo(
|
||||
() =>
|
||||
resultSets.find((resultSet) => resultSet.schema.name === selectedTable),
|
||||
[resultSets, selectedTable],
|
||||
);
|
||||
const nonemptyRawResults = resultSets.some(
|
||||
(resultSet) => resultSet.t === "RawResultSet" && resultSet.rows.length > 0,
|
||||
);
|
||||
|
||||
render(): React.ReactNode {
|
||||
const { selectedTable } = this.state;
|
||||
const resultSets = getResultSets(
|
||||
this.props.rawResultSets,
|
||||
this.props.interpretation,
|
||||
);
|
||||
const resultSetNames = getResultSetNames(
|
||||
this.props.interpretation,
|
||||
this.props.parsedResultSets,
|
||||
);
|
||||
|
||||
const resultSet = resultSets.find(
|
||||
(resultSet) => resultSet.schema.name === selectedTable,
|
||||
);
|
||||
const nonemptyRawResults = resultSets.some(
|
||||
(resultSet) =>
|
||||
resultSet.t === "RawResultSet" && resultSet.rows.length > 0,
|
||||
);
|
||||
const numberOfResults = resultSet && renderResultCountString(resultSet);
|
||||
|
||||
const resultSetOptions = resultSetNames.map((name) => (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
<ResultTablesHeader
|
||||
{...this.props}
|
||||
selectedTable={this.state.selectedTable}
|
||||
const resultSetOptions = resultSetNames.map((name) => (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
<ResultTablesHeader {...props} selectedTable={selectedTable} />
|
||||
<div className={tableHeaderClassName}></div>
|
||||
<div className={tableHeaderClassName}>
|
||||
<select value={selectedTable} onChange={onTableSelectionChange}>
|
||||
{resultSetOptions}
|
||||
</select>
|
||||
<ResultCount resultSet={resultSet} />
|
||||
<ProblemsViewCheckbox
|
||||
selectedTable={selectedTable}
|
||||
problemsViewSelected={problemsViewSelected}
|
||||
handleCheckboxChanged={handleCheckboxChanged}
|
||||
/>
|
||||
<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>
|
||||
{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 });
|
||||
sendTelemetry("local-results-show-raw-results");
|
||||
}}
|
||||
offset={this.getOffset()}
|
||||
/>
|
||||
)}
|
||||
{isLoadingNewResults ? (
|
||||
<span className={UPDATING_RESULTS_TEXT_CLASS_NAME}>
|
||||
Updating results…
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleMessage(msg: IntoResultsViewMsg): void {
|
||||
switch (msg.t) {
|
||||
case "untoggleShowProblems":
|
||||
this.setState({
|
||||
problemsViewSelected: false,
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Duplicated from results.tsx consider a way to
|
||||
// avoid this duplication
|
||||
componentDidMount(): void {
|
||||
this.vscodeMessageHandler = this.vscodeMessageHandler.bind(this);
|
||||
window.addEventListener("message", this.vscodeMessageHandler);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this.vscodeMessageHandler) {
|
||||
window.removeEventListener("message", this.vscodeMessageHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private vscodeMessageHandler(evt: MessageEvent) {
|
||||
// sanitize origin
|
||||
const origin = evt.origin.replace(/\n|\r/g, "");
|
||||
evt.origin === window.origin
|
||||
? this.handleMessage(evt.data as IntoResultsViewMsg)
|
||||
: console.error(`Invalid event origin ${origin}`);
|
||||
}
|
||||
{resultSet && (
|
||||
<ResultTable
|
||||
key={resultSet.schema.name}
|
||||
resultSet={resultSet}
|
||||
databaseUri={database.databaseUri}
|
||||
resultsPath={resultsPath}
|
||||
sortState={sortStates.get(resultSet.schema.name)}
|
||||
nonemptyRawResults={nonemptyRawResults}
|
||||
showRawResults={() => {
|
||||
setSelectedTable(SELECT_TABLE_NAME);
|
||||
sendTelemetry("local-results-show-raw-results");
|
||||
}}
|
||||
offset={offset}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getDefaultResultSet(resultSets: readonly ResultSet[]): string {
|
||||
|
||||
@@ -52,9 +52,11 @@ export function Graph({ graphData, databaseUri }: GraphProps) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if ("fill" in d.attributes) {
|
||||
d.attributes.fill = d.tag === "text" ? color : backgroundColor;
|
||||
d.attributes.fill = backgroundColor;
|
||||
}
|
||||
if (d.tag === "text") {
|
||||
d.attributes.fill = color;
|
||||
}
|
||||
if ("stroke" in d.attributes) {
|
||||
// There is no proper way to identify the element containing the graph (which we
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { CodeQLCliServer } from "../../../../../src/codeql-cli/cli";
|
||||
import { ExternalApiUsage } from "../../../../../src/data-extensions-editor/external-api-usage";
|
||||
import { ModelDetailsDataProvider } from "../../../../../src/data-extensions-editor/model-details/model-details-data-provider";
|
||||
import { DatabaseItem } from "../../../../../src/databases/local-databases";
|
||||
import { mockedObject } from "../../../utils/mocking.helpers";
|
||||
|
||||
describe("ModelDetailsDataProvider", () => {
|
||||
const mockCliServer = mockedObject<CodeQLCliServer>({});
|
||||
|
||||
describe("setState", () => {
|
||||
it("should not emit onDidChangeTreeData event when state has not changed", async () => {
|
||||
const externalApiUsages: ExternalApiUsage[] = [];
|
||||
const dbItem = mockedObject<DatabaseItem>({
|
||||
getSourceLocationPrefix: () => "test",
|
||||
});
|
||||
|
||||
const dataProvider = new ModelDetailsDataProvider(mockCliServer);
|
||||
await dataProvider.setState(externalApiUsages, dbItem);
|
||||
|
||||
const onDidChangeTreeDataListener = jest.fn();
|
||||
dataProvider.onDidChangeTreeData(onDidChangeTreeDataListener);
|
||||
|
||||
await dataProvider.setState(externalApiUsages, dbItem);
|
||||
|
||||
expect(onDidChangeTreeDataListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should emit onDidChangeTreeData event when externalApiUsages has changed", async () => {
|
||||
const externalApiUsages1: ExternalApiUsage[] = [];
|
||||
const externalApiUsages2: ExternalApiUsage[] = [];
|
||||
const dbItem = mockedObject<DatabaseItem>({
|
||||
getSourceLocationPrefix: () => "test",
|
||||
});
|
||||
|
||||
const dataProvider = new ModelDetailsDataProvider(mockCliServer);
|
||||
await dataProvider.setState(externalApiUsages1, dbItem);
|
||||
|
||||
const onDidChangeTreeDataListener = jest.fn();
|
||||
dataProvider.onDidChangeTreeData(onDidChangeTreeDataListener);
|
||||
|
||||
await dataProvider.setState(externalApiUsages2, dbItem);
|
||||
|
||||
expect(onDidChangeTreeDataListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should emit onDidChangeTreeData event when dbItem has changed", async () => {
|
||||
const externalApiUsages: ExternalApiUsage[] = [];
|
||||
const dbItem1 = mockedObject<DatabaseItem>({
|
||||
getSourceLocationPrefix: () => "test",
|
||||
});
|
||||
const dbItem2 = mockedObject<DatabaseItem>({
|
||||
getSourceLocationPrefix: () => "test",
|
||||
});
|
||||
|
||||
const dataProvider = new ModelDetailsDataProvider(mockCliServer);
|
||||
await dataProvider.setState(externalApiUsages, dbItem1);
|
||||
|
||||
const onDidChangeTreeDataListener = jest.fn();
|
||||
dataProvider.onDidChangeTreeData(onDidChangeTreeDataListener);
|
||||
|
||||
await dataProvider.setState(externalApiUsages, dbItem2);
|
||||
|
||||
expect(onDidChangeTreeDataListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user