Wire up processing model evaluation run results and showing alerts (#3503)

This commit is contained in:
Charis Kyriakou
2024-03-22 14:33:36 +00:00
committed by GitHub
parent 5c43018692
commit cbc6b73759
7 changed files with 224 additions and 7 deletions

View File

@@ -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

View File

@@ -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: [],
};
}

View File

@@ -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();
} }

View File

@@ -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,
);
}
} }

View File

@@ -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> {

View File

@@ -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);

View File

@@ -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>
</> </>
); );