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 коммит произвёл GitHub
Родитель 5c43018692
Коммит cbc6b73759
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
7 изменённых файлов: 224 добавлений и 7 удалений

Просмотреть файл

@ -732,6 +732,11 @@ interface SetModelAlertsViewStateMessage {
viewState: ModelAlertsViewState;
}
interface SetReposResultsMessage {
t: "setReposResults";
reposResults: VariantAnalysisScannedRepositoryResult[];
}
interface OpenModelPackMessage {
t: "openModelPack";
path: string;
@ -749,7 +754,8 @@ interface StopEvaluationRunMessage {
export type ToModelAlertsMessage =
| SetModelAlertsViewStateMessage
| SetVariantAnalysisMessage
| SetRepoResultsMessage;
| SetRepoResultsMessage
| SetReposResultsMessage;
export type FromModelAlertsMessage =
| 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;
}
public async showView() {
public async showView(
reposResults: VariantAnalysisScannedRepositoryResult[],
) {
const panel = await this.getPanel();
panel.reveal(undefined, true);
await this.waitForPanelLoaded();
await this.setViewState();
await this.updateReposResults(reposResults);
}
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> {
this.panel?.reveal();
}

Просмотреть файл

@ -13,6 +13,7 @@ import {
UserCancellationException,
withProgress,
} from "../common/vscode/progress";
import { VariantAnalysisScannedRepositoryDownloadStatus } from "../variant-analysis/shared/variant-analysis";
import type { VariantAnalysis } from "../variant-analysis/shared/variant-analysis";
import type { CancellationToken } from "vscode";
import { CancellationTokenSource } from "vscode";
@ -126,7 +127,6 @@ export class ModelEvaluator extends DisposableObject {
this.dbItem,
this.extensionPack,
);
await this.modelAlertsView.showView();
this.modelAlertsView.onEvaluationRunStopClicked(async () => {
await this.stopEvaluation();
@ -148,6 +148,12 @@ export class ModelEvaluator extends DisposableObject {
throw new Error("No variant analysis available");
}
const reposResults =
this.variantAnalysisManager.getLoadedResultsForVariantAnalysis(
variantAnalysis.id,
);
await this.modelAlertsView.showView(reposResults);
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.variantAnalysisManager.onRepoResultsLoaded(async (e) => {
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(
variantAnalysisId: number,
): Promise<boolean> {

Просмотреть файл

@ -12,6 +12,7 @@ import { sarifParser } from "../common/sarif-parser";
import { extractAnalysisAlerts } from "./sarif-processing";
import type { CodeQLCliServer } from "../codeql-cli/cli";
import { extractRawResults } from "./bqrs-processing";
import { VariantAnalysisRepoStatus } from "./shared/variant-analysis";
import type {
VariantAnalysis,
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) {
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 { ToModelAlertsMessage } from "../../common/interface-types";
import type {
@ -6,7 +8,9 @@ import type {
VariantAnalysisScannedRepositoryResult,
} from "../../variant-analysis/shared/variant-analysis";
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 = {
initialViewState?: ModelAlertsViewState;
@ -14,6 +18,13 @@ type Props = {
repoResults?: VariantAnalysisScannedRepositoryResult[];
};
const SectionTitle = styled.h3`
font-size: medium;
font-weight: 500;
margin: 0;
padding-bottom: 10px;
`;
export function ModelAlerts({
initialViewState,
variantAnalysis: initialVariantAnalysis,
@ -55,6 +66,10 @@ export function ModelAlerts({
setVariantAnalysis(msg.variantAnalysis);
break;
}
case "setReposResults": {
setRepoResults(msg.reposResults);
break;
}
case "setRepoResults": {
setRepoResults((oldRepoResults) => {
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) {
return <></>;
}
@ -105,8 +130,16 @@ export function ModelAlerts({
stopRunClick={onStopRunClick}
></ModelAlertsHeader>
<div>
<h3>Repo results</h3>
<p>{JSON.stringify(repoResults, null, 2)}</p>
<SectionTitle>Model alerts</SectionTitle>
<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>
</>
);