Wire up processing model evaluation run results and showing alerts (#3503)
This commit is contained in:
Родитель
5c43018692
Коммит
cbc6b73759
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
Загрузка…
Ссылка в новой задаче