Wire up processing model evaluation run results and showing alerts (#3503)
This commit is contained in:
@@ -732,6 +732,11 @@ interface SetModelAlertsViewStateMessage {
|
|||||||
viewState: ModelAlertsViewState;
|
viewState: ModelAlertsViewState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SetReposResultsMessage {
|
||||||
|
t: "setReposResults";
|
||||||
|
reposResults: VariantAnalysisScannedRepositoryResult[];
|
||||||
|
}
|
||||||
|
|
||||||
interface OpenModelPackMessage {
|
interface OpenModelPackMessage {
|
||||||
t: "openModelPack";
|
t: "openModelPack";
|
||||||
path: string;
|
path: string;
|
||||||
@@ -749,7 +754,8 @@ interface StopEvaluationRunMessage {
|
|||||||
export type ToModelAlertsMessage =
|
export type ToModelAlertsMessage =
|
||||||
| SetModelAlertsViewStateMessage
|
| SetModelAlertsViewStateMessage
|
||||||
| SetVariantAnalysisMessage
|
| SetVariantAnalysisMessage
|
||||||
| SetRepoResultsMessage;
|
| SetRepoResultsMessage
|
||||||
|
| SetReposResultsMessage;
|
||||||
|
|
||||||
export type FromModelAlertsMessage =
|
export type FromModelAlertsMessage =
|
||||||
| CommonFromViewMessages
|
| CommonFromViewMessages
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import type { AnalysisAlert } from "../../variant-analysis/shared/analysis-result";
|
||||||
|
import type { ModeledMethod } from "../modeled-method";
|
||||||
|
import { EndpointType } from "../method";
|
||||||
|
import type { ModelAlerts } from "./model-alerts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate which model has contributed to each alert.
|
||||||
|
* @param alerts The alerts to process.
|
||||||
|
* @returns The alerts grouped by modeled method.
|
||||||
|
*/
|
||||||
|
export function calculateModelAlerts(alerts: AnalysisAlert[]): ModelAlerts[] {
|
||||||
|
// Temporary logging to use alerts variable.
|
||||||
|
console.log(`Processing ${alerts.length} alerts`);
|
||||||
|
|
||||||
|
// For now we just return some mock data, but once we have provenance information
|
||||||
|
// we'll be able to calculate this properly based on the alerts that are passed in
|
||||||
|
// and potentially some other information.
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
model: createModeledMethod(),
|
||||||
|
alerts: [createMockAlert()],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function createModeledMethod(): ModeledMethod {
|
||||||
|
return {
|
||||||
|
libraryVersion: "1.6.0",
|
||||||
|
signature: "org.sql2o.Connection#createQuery(String)",
|
||||||
|
endpointType: EndpointType.Method,
|
||||||
|
packageName: "org.sql2o",
|
||||||
|
typeName: "Connection",
|
||||||
|
methodName: "createQuery",
|
||||||
|
methodParameters: "(String)",
|
||||||
|
type: "sink",
|
||||||
|
input: "Argument[0]",
|
||||||
|
kind: "path-injection",
|
||||||
|
provenance: "manual",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockAlert(): AnalysisAlert {
|
||||||
|
return {
|
||||||
|
message: {
|
||||||
|
tokens: [
|
||||||
|
{
|
||||||
|
t: "text",
|
||||||
|
text: "This is an empty block.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
shortDescription: "This is an empty block.",
|
||||||
|
fileLink: {
|
||||||
|
fileLinkPrefix:
|
||||||
|
"https://github.com/expressjs/express/blob/33e8dc303af9277f8a7e4f46abfdcb5e72f6797b",
|
||||||
|
filePath: "test/app.options.js",
|
||||||
|
},
|
||||||
|
severity: "Warning",
|
||||||
|
codeSnippet: {
|
||||||
|
startLine: 10,
|
||||||
|
endLine: 14,
|
||||||
|
text: " app.del('/', function(){});\n app.get('/users', function(req, res){});\n app.put('/users', function(req, res){});\n\n request(app)\n",
|
||||||
|
},
|
||||||
|
highlightedRegion: {
|
||||||
|
startLine: 12,
|
||||||
|
startColumn: 41,
|
||||||
|
endLine: 12,
|
||||||
|
endColumn: 43,
|
||||||
|
},
|
||||||
|
codeFlows: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -48,12 +48,15 @@ export class ModelAlertsView extends AbstractWebview<
|
|||||||
this.onEvaluationRunStopClickedEventEmitter.event;
|
this.onEvaluationRunStopClickedEventEmitter.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async showView() {
|
public async showView(
|
||||||
|
reposResults: VariantAnalysisScannedRepositoryResult[],
|
||||||
|
) {
|
||||||
const panel = await this.getPanel();
|
const panel = await this.getPanel();
|
||||||
panel.reveal(undefined, true);
|
panel.reveal(undefined, true);
|
||||||
|
|
||||||
await this.waitForPanelLoaded();
|
await this.waitForPanelLoaded();
|
||||||
await this.setViewState();
|
await this.setViewState();
|
||||||
|
await this.updateReposResults(reposResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
|
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
|
||||||
@@ -139,6 +142,19 @@ export class ModelAlertsView extends AbstractWebview<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateReposResults(
|
||||||
|
reposResults: VariantAnalysisScannedRepositoryResult[],
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.isShowingPanel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.postMessage({
|
||||||
|
t: "setReposResults",
|
||||||
|
reposResults,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async focusView(): Promise<void> {
|
public async focusView(): Promise<void> {
|
||||||
this.panel?.reveal();
|
this.panel?.reveal();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
UserCancellationException,
|
UserCancellationException,
|
||||||
withProgress,
|
withProgress,
|
||||||
} from "../common/vscode/progress";
|
} from "../common/vscode/progress";
|
||||||
|
import { VariantAnalysisScannedRepositoryDownloadStatus } from "../variant-analysis/shared/variant-analysis";
|
||||||
import type { VariantAnalysis } from "../variant-analysis/shared/variant-analysis";
|
import type { VariantAnalysis } from "../variant-analysis/shared/variant-analysis";
|
||||||
import type { CancellationToken } from "vscode";
|
import type { CancellationToken } from "vscode";
|
||||||
import { CancellationTokenSource } from "vscode";
|
import { CancellationTokenSource } from "vscode";
|
||||||
@@ -126,7 +127,6 @@ export class ModelEvaluator extends DisposableObject {
|
|||||||
this.dbItem,
|
this.dbItem,
|
||||||
this.extensionPack,
|
this.extensionPack,
|
||||||
);
|
);
|
||||||
await this.modelAlertsView.showView();
|
|
||||||
|
|
||||||
this.modelAlertsView.onEvaluationRunStopClicked(async () => {
|
this.modelAlertsView.onEvaluationRunStopClicked(async () => {
|
||||||
await this.stopEvaluation();
|
await this.stopEvaluation();
|
||||||
@@ -148,6 +148,12 @@ export class ModelEvaluator extends DisposableObject {
|
|||||||
throw new Error("No variant analysis available");
|
throw new Error("No variant analysis available");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reposResults =
|
||||||
|
this.variantAnalysisManager.getLoadedResultsForVariantAnalysis(
|
||||||
|
variantAnalysis.id,
|
||||||
|
);
|
||||||
|
await this.modelAlertsView.showView(reposResults);
|
||||||
|
|
||||||
await this.modelAlertsView.updateVariantAnalysis(variantAnalysis);
|
await this.modelAlertsView.updateVariantAnalysis(variantAnalysis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,6 +266,21 @@ export class ModelEvaluator extends DisposableObject {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.push(
|
||||||
|
this.variantAnalysisManager.onRepoStatesUpdated(async (e) => {
|
||||||
|
if (
|
||||||
|
e.variantAnalysisId === variantAnalysisId &&
|
||||||
|
e.repoState.downloadStatus ===
|
||||||
|
VariantAnalysisScannedRepositoryDownloadStatus.Succeeded
|
||||||
|
) {
|
||||||
|
await this.readAnalysisResults(
|
||||||
|
variantAnalysisId,
|
||||||
|
e.repoState.repositoryId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
this.push(
|
this.push(
|
||||||
this.variantAnalysisManager.onRepoResultsLoaded(async (e) => {
|
this.variantAnalysisManager.onRepoResultsLoaded(async (e) => {
|
||||||
if (e.variantAnalysisId === variantAnalysisId) {
|
if (e.variantAnalysisId === variantAnalysisId) {
|
||||||
@@ -268,4 +289,39 @@ export class ModelEvaluator extends DisposableObject {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async readAnalysisResults(
|
||||||
|
variantAnalysisId: number,
|
||||||
|
repositoryId: number,
|
||||||
|
) {
|
||||||
|
const variantAnalysis =
|
||||||
|
this.variantAnalysisManager.tryGetVariantAnalysis(variantAnalysisId);
|
||||||
|
if (!variantAnalysis) {
|
||||||
|
void this.app.logger.log(
|
||||||
|
`Could not find variant analysis with id ${variantAnalysisId}`,
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
"There was an error when trying to retrieve variant analysis information",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const repository = variantAnalysis.scannedRepos?.find(
|
||||||
|
(r) => r.repository.id === repositoryId,
|
||||||
|
);
|
||||||
|
if (!repository) {
|
||||||
|
void this.app.logger.log(
|
||||||
|
`Could not find repository with id ${repositoryId} in scanned repos`,
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
"There was an error when trying to retrieve repository information",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger loading the results for the repository. This will trigger a
|
||||||
|
// onRepoResultsLoaded event that we'll process.
|
||||||
|
await this.variantAnalysisManager.loadResults(
|
||||||
|
variantAnalysisId,
|
||||||
|
repository.repository.fullName,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -635,6 +635,17 @@ export class VariantAnalysisManager
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getLoadedResultsForVariantAnalysis(variantAnalysisId: number) {
|
||||||
|
const variantAnalysis = this.variantAnalyses.get(variantAnalysisId);
|
||||||
|
if (!variantAnalysis) {
|
||||||
|
throw new Error(`No variant analysis with id: ${variantAnalysisId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.variantAnalysisResultsManager.getLoadedResultsForVariantAnalysis(
|
||||||
|
variantAnalysis,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async variantAnalysisRecordExists(
|
private async variantAnalysisRecordExists(
|
||||||
variantAnalysisId: number,
|
variantAnalysisId: number,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { sarifParser } from "../common/sarif-parser";
|
|||||||
import { extractAnalysisAlerts } from "./sarif-processing";
|
import { extractAnalysisAlerts } from "./sarif-processing";
|
||||||
import type { CodeQLCliServer } from "../codeql-cli/cli";
|
import type { CodeQLCliServer } from "../codeql-cli/cli";
|
||||||
import { extractRawResults } from "./bqrs-processing";
|
import { extractRawResults } from "./bqrs-processing";
|
||||||
|
import { VariantAnalysisRepoStatus } from "./shared/variant-analysis";
|
||||||
import type {
|
import type {
|
||||||
VariantAnalysis,
|
VariantAnalysis,
|
||||||
VariantAnalysisRepositoryTask,
|
VariantAnalysisRepositoryTask,
|
||||||
@@ -305,6 +306,28 @@ export class VariantAnalysisResultsManager extends DisposableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getLoadedResultsForVariantAnalysis(
|
||||||
|
variantAnalysis: VariantAnalysis,
|
||||||
|
): VariantAnalysisScannedRepositoryResult[] {
|
||||||
|
const scannedRepos = variantAnalysis.scannedRepos?.filter(
|
||||||
|
(r) => r.analysisStatus === VariantAnalysisRepoStatus.Succeeded,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!scannedRepos) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return scannedRepos
|
||||||
|
.map((scannedRepo) =>
|
||||||
|
this.cachedResults.get(
|
||||||
|
createCacheKey(variantAnalysis.id, scannedRepo.repository.fullName),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
(r): r is VariantAnalysisScannedRepositoryResult => r !== undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public dispose(disposeHandler?: DisposeHandler) {
|
public dispose(disposeHandler?: DisposeHandler) {
|
||||||
super.dispose(disposeHandler);
|
super.dispose(disposeHandler);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { styled } from "styled-components";
|
||||||
|
import { ModelAlertsHeader } from "./ModelAlertsHeader";
|
||||||
import type { ModelAlertsViewState } from "../../model-editor/shared/view-state";
|
import type { ModelAlertsViewState } from "../../model-editor/shared/view-state";
|
||||||
import type { ToModelAlertsMessage } from "../../common/interface-types";
|
import type { ToModelAlertsMessage } from "../../common/interface-types";
|
||||||
import type {
|
import type {
|
||||||
@@ -6,7 +8,9 @@ import type {
|
|||||||
VariantAnalysisScannedRepositoryResult,
|
VariantAnalysisScannedRepositoryResult,
|
||||||
} from "../../variant-analysis/shared/variant-analysis";
|
} from "../../variant-analysis/shared/variant-analysis";
|
||||||
import { vscode } from "../vscode-api";
|
import { vscode } from "../vscode-api";
|
||||||
import { ModelAlertsHeader } from "./ModelAlertsHeader";
|
import { ModelAlertsResults } from "./ModelAlertsResults";
|
||||||
|
import type { ModelAlerts } from "../../model-editor/model-alerts/model-alerts";
|
||||||
|
import { calculateModelAlerts } from "../../model-editor/model-alerts/alert-processor";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
initialViewState?: ModelAlertsViewState;
|
initialViewState?: ModelAlertsViewState;
|
||||||
@@ -14,6 +18,13 @@ type Props = {
|
|||||||
repoResults?: VariantAnalysisScannedRepositoryResult[];
|
repoResults?: VariantAnalysisScannedRepositoryResult[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SectionTitle = styled.h3`
|
||||||
|
font-size: medium;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
export function ModelAlerts({
|
export function ModelAlerts({
|
||||||
initialViewState,
|
initialViewState,
|
||||||
variantAnalysis: initialVariantAnalysis,
|
variantAnalysis: initialVariantAnalysis,
|
||||||
@@ -55,6 +66,10 @@ export function ModelAlerts({
|
|||||||
setVariantAnalysis(msg.variantAnalysis);
|
setVariantAnalysis(msg.variantAnalysis);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "setReposResults": {
|
||||||
|
setRepoResults(msg.reposResults);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "setRepoResults": {
|
case "setRepoResults": {
|
||||||
setRepoResults((oldRepoResults) => {
|
setRepoResults((oldRepoResults) => {
|
||||||
const newRepoIds = msg.repoResults.map((r) => r.repositoryId);
|
const newRepoIds = msg.repoResults.map((r) => r.repositoryId);
|
||||||
@@ -81,6 +96,16 @@ export function ModelAlerts({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const modelAlerts = useMemo(() => {
|
||||||
|
if (!repoResults) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const alerts = repoResults.flatMap((a) => a.interpretedResults ?? []);
|
||||||
|
|
||||||
|
return calculateModelAlerts(alerts);
|
||||||
|
}, [repoResults]);
|
||||||
|
|
||||||
if (viewState === undefined || variantAnalysis === undefined) {
|
if (viewState === undefined || variantAnalysis === undefined) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@@ -105,8 +130,16 @@ export function ModelAlerts({
|
|||||||
stopRunClick={onStopRunClick}
|
stopRunClick={onStopRunClick}
|
||||||
></ModelAlertsHeader>
|
></ModelAlertsHeader>
|
||||||
<div>
|
<div>
|
||||||
<h3>Repo results</h3>
|
<SectionTitle>Model alerts</SectionTitle>
|
||||||
<p>{JSON.stringify(repoResults, null, 2)}</p>
|
<div>
|
||||||
|
{modelAlerts.map((alerts, i) => (
|
||||||
|
// We're using the index as the key here which is not recommended.
|
||||||
|
// but we don't have a unique identifier for models. In the future,
|
||||||
|
// we may need to consider coming up with unique identifiers for models
|
||||||
|
// and using those as keys.
|
||||||
|
<ModelAlertsResults key={i} modelAlerts={alerts} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user