Add commands for recording of scenario
This will add the commands and the implementation in the `MockGitHubApiServer` for the recording of a scenario.
This commit is contained in:
@@ -645,6 +645,18 @@
|
||||
"command": "codeQL.gotoQL",
|
||||
"title": "CodeQL: Go to QL Code",
|
||||
"enablement": "codeql.hasQLSource"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.mockGitHubApiServer.startRecording",
|
||||
"title": "CodeQL Mock GitHub API Server: Start Scenario Recording"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.mockGitHubApiServer.saveScenario",
|
||||
"title": "CodeQL Mock GitHub API Server: Save Scenario"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.mockGitHubApiServer.cancelRecording",
|
||||
"title": "CodeQL Mock GitHub API Server: Cancel Scenario"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
@@ -1104,6 +1116,18 @@
|
||||
{
|
||||
"command": "codeQLTests.showOutputDifferences",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.mockGitHubApiServer.startRecording",
|
||||
"when": "config.codeQL.variantAnalysis.mockGitHubApiServer && !codeQL.mockGitHubApiServer.recording"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.mockGitHubApiServer.saveScenario",
|
||||
"when": "codeQL.mockGitHubApiServer.recording"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.mockGitHubApiServer.cancelRecording",
|
||||
"when": "codeQL.mockGitHubApiServer.recording"
|
||||
}
|
||||
],
|
||||
"editor/context": [
|
||||
|
||||
@@ -449,3 +449,9 @@ export class MockGitHubApiConfigListener extends ConfigListener implements MockG
|
||||
return !!MOCK_GH_API_SERVER.getValue<boolean>();
|
||||
}
|
||||
}
|
||||
|
||||
const MOCK_GH_API_SERVER_SCENARIOS_PATH = new Setting('mockGitHubApiServerScenariosPath', REMOTE_QUERIES_SETTING);
|
||||
|
||||
export function getMockGitHubApiServerScenariosPath(): string | undefined {
|
||||
return MOCK_GH_API_SERVER_SCENARIOS_PATH.getValue<string>();
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ import {
|
||||
} from './remote-queries/gh-api/variant-analysis';
|
||||
import { VariantAnalysisManager } from './remote-queries/variant-analysis-manager';
|
||||
import { createVariantAnalysisContentProvider } from './remote-queries/variant-analysis-content-provider';
|
||||
import { MockGitHubApiServer } from './mocks/mock-gh-api-server';
|
||||
|
||||
/**
|
||||
* extension.ts
|
||||
@@ -1190,6 +1191,27 @@ async function activateWithInstalledDistribution(
|
||||
)
|
||||
);
|
||||
|
||||
const mockServer = new MockGitHubApiServer();
|
||||
ctx.subscriptions.push(mockServer);
|
||||
ctx.subscriptions.push(
|
||||
commandRunner(
|
||||
'codeQL.mockGitHubApiServer.startRecording',
|
||||
async () => await mockServer.recordScenario(),
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commandRunner(
|
||||
'codeQL.mockGitHubApiServer.saveScenario',
|
||||
async () => await mockServer.saveScenario(),
|
||||
)
|
||||
);
|
||||
ctx.subscriptions.push(
|
||||
commandRunner(
|
||||
'codeQL.mockGitHubApiServer.cancelRecording',
|
||||
async () => await mockServer.cancelRecording(),
|
||||
)
|
||||
);
|
||||
|
||||
await commands.executeCommand('codeQLDatabases.removeOrphanedDatabases');
|
||||
|
||||
void logger.log('Successfully finished extension initialization.');
|
||||
|
||||
@@ -1,28 +1,44 @@
|
||||
import { MockGitHubApiConfigListener } from '../config';
|
||||
|
||||
import { setupServer, SetupServerApi } from 'msw/node';
|
||||
import { Recorder } from './recorder';
|
||||
import { commands, env, Uri, window } from 'vscode';
|
||||
import { DisposableObject } from '../pure/disposable-object';
|
||||
import { getMockGitHubApiServerScenariosPath } from '../config';
|
||||
|
||||
/**
|
||||
* Enables mocking of the GitHub API server via HTTP interception, using msw.
|
||||
*/
|
||||
export class MockGitHubApiServer {
|
||||
export class MockGitHubApiServer extends DisposableObject {
|
||||
private isListening: boolean;
|
||||
private config: MockGitHubApiConfigListener;
|
||||
|
||||
private readonly server: SetupServerApi;
|
||||
private readonly recorder: Recorder;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isListening = false;
|
||||
this.config = new MockGitHubApiConfigListener();
|
||||
|
||||
this.server = setupServer();
|
||||
this.recorder = this.push(new Recorder(this.server));
|
||||
|
||||
this.setupConfigListener();
|
||||
}
|
||||
|
||||
public startServer(): void {
|
||||
this.isListening = true;
|
||||
if (this.isListening) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Enable HTTP interception.
|
||||
this.server.listen();
|
||||
this.isListening = true;
|
||||
}
|
||||
|
||||
public stopServer(): void {
|
||||
this.server.close();
|
||||
this.isListening = false;
|
||||
|
||||
// TODO: Disable HTTP interception.
|
||||
}
|
||||
|
||||
public loadScenario(): void {
|
||||
@@ -33,8 +49,98 @@ export class MockGitHubApiServer {
|
||||
// TODO: Implement logic to list all available scenarios.
|
||||
}
|
||||
|
||||
public recordScenario(): void {
|
||||
// TODO: Implement logic to record a new scenario to a directory.
|
||||
public async recordScenario(): Promise<void> {
|
||||
if (this.recorder.isRecording) {
|
||||
void window.showErrorMessage('A scenario is already being recorded. Use the "Save Scenario" or "Cancel Scenario" commands to finish recording.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.recorder.start();
|
||||
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.recording', true);
|
||||
|
||||
await window.showInformationMessage('Recording scenario. To save the scenario, use the "CodeQL Mock GitHub API Server: Save Scenario" command.');
|
||||
}
|
||||
|
||||
public async saveScenario(): Promise<void> {
|
||||
const scenariosDirectory = await this.getScenariosDirectory();
|
||||
if (!scenariosDirectory) {
|
||||
return;
|
||||
}
|
||||
|
||||
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.recording', false);
|
||||
|
||||
if (!this.recorder.isRecording) {
|
||||
void window.showErrorMessage('No scenario is currently being recorded.');
|
||||
return;
|
||||
}
|
||||
if (this.recorder.scenarioRequestCount === 0) {
|
||||
void window.showWarningMessage('No requests were recorded. Cancelling scenario.');
|
||||
|
||||
await this.stopRecording();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const name = await window.showInputBox({
|
||||
title: 'Save scenario',
|
||||
prompt: 'Enter a name for the scenario.',
|
||||
placeHolder: 'successful-run',
|
||||
});
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filepath = await this.recorder.save(scenariosDirectory, name);
|
||||
|
||||
await this.stopRecording();
|
||||
|
||||
const action = await window.showInformationMessage(`Scenario saved to ${filepath}`, 'Open directory');
|
||||
if (action === 'Open directory') {
|
||||
await env.openExternal(Uri.file(filepath));
|
||||
}
|
||||
}
|
||||
|
||||
public async cancelRecording(): Promise<void> {
|
||||
if (!this.recorder.isRecording) {
|
||||
void window.showErrorMessage('No scenario is currently being recorded.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.stopRecording();
|
||||
|
||||
void window.showInformationMessage('Recording cancelled.');
|
||||
}
|
||||
|
||||
private async stopRecording(): Promise<void> {
|
||||
await commands.executeCommand('setContext', 'codeQL.mockGitHubApiServer.recording', false);
|
||||
|
||||
await this.recorder.stop();
|
||||
await this.recorder.clear();
|
||||
}
|
||||
|
||||
private async getScenariosDirectory(): Promise<string | undefined> {
|
||||
const scenariosDirectory = getMockGitHubApiServerScenariosPath();
|
||||
if (scenariosDirectory) {
|
||||
return scenariosDirectory;
|
||||
}
|
||||
|
||||
const directories = await window.showOpenDialog({
|
||||
canSelectFolders: true,
|
||||
canSelectFiles: false,
|
||||
canSelectMany: false,
|
||||
openLabel: 'Select scenarios directory',
|
||||
title: 'Select scenarios directory',
|
||||
});
|
||||
if (directories === undefined || directories.length === 0) {
|
||||
void window.showErrorMessage('No scenarios directory selected.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Unfortunately, we cannot save the directory in the configuration because that requires
|
||||
// the configuration to be registered. If we do that, it would be visible to all users; there
|
||||
// is no "when" clause that would allow us to only show it to users who have enabled the feature flag.
|
||||
|
||||
return directories[0].fsPath;
|
||||
}
|
||||
|
||||
private setupConfigListener(): void {
|
||||
|
||||
Reference in New Issue
Block a user