Add empty states for modeling panel (#2914)
This commit is contained in:
@@ -323,6 +323,7 @@ export type PackagingCommands = {
|
||||
|
||||
export type ModelEditorCommands = {
|
||||
"codeQL.openModelEditor": () => Promise<void>;
|
||||
"codeQL.openModelEditorFromModelingPanel": () => Promise<void>;
|
||||
"codeQLModelEditor.jumpToUsageLocation": (
|
||||
method: Method,
|
||||
usage: Usage,
|
||||
|
||||
@@ -610,10 +610,15 @@ interface RevealInEditorMessage {
|
||||
method: Method;
|
||||
}
|
||||
|
||||
interface StartModelingMessage {
|
||||
t: "startModeling";
|
||||
}
|
||||
|
||||
export type FromMethodModelingMessage =
|
||||
| CommonFromViewMessages
|
||||
| SetModeledMethodMessage
|
||||
| RevealInEditorMessage;
|
||||
| RevealInEditorMessage
|
||||
| StartModelingMessage;
|
||||
|
||||
interface SetMethodMessage {
|
||||
t: "setMethod";
|
||||
|
||||
@@ -13,7 +13,7 @@ export abstract class AbstractWebviewViewProvider<
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
protected readonly app: App,
|
||||
private readonly webviewKind: WebviewKind,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -92,6 +92,12 @@ export class MethodModelingViewProvider extends AbstractWebviewViewProvider<
|
||||
await this.revealInModelEditor(msg.method);
|
||||
|
||||
break;
|
||||
|
||||
case "startModeling":
|
||||
await this.app.commands.execute(
|
||||
"codeQL.openModelEditorFromModelingPanel",
|
||||
);
|
||||
break;
|
||||
default:
|
||||
assertNever(msg);
|
||||
}
|
||||
|
||||
@@ -73,115 +73,9 @@ export class ModelEditorModule extends DisposableObject {
|
||||
|
||||
public getCommands(): ModelEditorCommands {
|
||||
return {
|
||||
"codeQL.openModelEditor": async () => {
|
||||
const db = this.databaseManager.currentDatabaseItem;
|
||||
if (!db) {
|
||||
void showAndLogErrorMessage(this.app.logger, "No database selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const language = db.language;
|
||||
if (
|
||||
!SUPPORTED_LANGUAGES.includes(language) ||
|
||||
!isQueryLanguage(language)
|
||||
) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`The CodeQL Model Editor is not supported for ${language} databases.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
return withProgress(
|
||||
async (progress) => {
|
||||
const maxStep = 4;
|
||||
|
||||
if (!(await this.cliServer.cliConstraints.supportsQlpacksKind())) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND.format()} or later.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(await this.cliServer.cliConstraints.supportsResolveExtensions())
|
||||
) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS.format()} or later.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const modelFile = await pickExtensionPack(
|
||||
this.cliServer,
|
||||
db,
|
||||
this.app.logger,
|
||||
progress,
|
||||
maxStep,
|
||||
);
|
||||
if (!modelFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
message: "Installing dependencies...",
|
||||
step: 3,
|
||||
maxStep,
|
||||
});
|
||||
|
||||
// Create new temporary directory for query files and pack dependencies
|
||||
const { path: queryDir, cleanup: cleanupQueryDir } = await dir({
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
const success = await setUpPack(this.cliServer, queryDir, language);
|
||||
if (!success) {
|
||||
await cleanupQueryDir();
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
message: "Opening editor...",
|
||||
step: 4,
|
||||
maxStep,
|
||||
});
|
||||
|
||||
const view = new ModelEditorView(
|
||||
this.app,
|
||||
this.modelingStore,
|
||||
this.editorViewTracker,
|
||||
this.databaseManager,
|
||||
this.cliServer,
|
||||
this.queryRunner,
|
||||
this.queryStorageDir,
|
||||
queryDir,
|
||||
db,
|
||||
modelFile,
|
||||
Mode.Application,
|
||||
);
|
||||
|
||||
this.modelingStore.onDbClosed(async (dbUri) => {
|
||||
if (dbUri === db.databaseUri.toString()) {
|
||||
await cleanupQueryDir();
|
||||
}
|
||||
});
|
||||
|
||||
this.push(view);
|
||||
this.push({
|
||||
dispose(): void {
|
||||
void cleanupQueryDir();
|
||||
},
|
||||
});
|
||||
|
||||
await view.openView();
|
||||
},
|
||||
{
|
||||
title: "Opening CodeQL Model Editor",
|
||||
},
|
||||
);
|
||||
},
|
||||
"codeQL.openModelEditor": this.openModelEditor.bind(this),
|
||||
"codeQL.openModelEditorFromModelingPanel":
|
||||
this.openModelEditor.bind(this),
|
||||
"codeQLModelEditor.jumpToUsageLocation": async (
|
||||
method: Method,
|
||||
usage: Usage,
|
||||
@@ -213,4 +107,116 @@ export class ModelEditorModule extends DisposableObject {
|
||||
await this.methodModelingPanel.setMethod(method);
|
||||
await showResolvableLocation(usage.url, databaseItem, this.app.logger);
|
||||
}
|
||||
|
||||
private async openModelEditor(): Promise<void> {
|
||||
{
|
||||
const db = this.databaseManager.currentDatabaseItem;
|
||||
if (!db) {
|
||||
void showAndLogErrorMessage(this.app.logger, "No database selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const language = db.language;
|
||||
if (
|
||||
!SUPPORTED_LANGUAGES.includes(language) ||
|
||||
!isQueryLanguage(language)
|
||||
) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`The CodeQL Model Editor is not supported for ${language} databases.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
return withProgress(
|
||||
async (progress) => {
|
||||
const maxStep = 4;
|
||||
|
||||
if (!(await this.cliServer.cliConstraints.supportsQlpacksKind())) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND.format()} or later.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(await this.cliServer.cliConstraints.supportsResolveExtensions())
|
||||
) {
|
||||
void showAndLogErrorMessage(
|
||||
this.app.logger,
|
||||
`This feature requires CodeQL CLI version ${CliVersionConstraint.CLI_VERSION_WITH_RESOLVE_EXTENSIONS.format()} or later.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const modelFile = await pickExtensionPack(
|
||||
this.cliServer,
|
||||
db,
|
||||
this.app.logger,
|
||||
progress,
|
||||
maxStep,
|
||||
);
|
||||
if (!modelFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
message: "Installing dependencies...",
|
||||
step: 3,
|
||||
maxStep,
|
||||
});
|
||||
|
||||
// Create new temporary directory for query files and pack dependencies
|
||||
const { path: queryDir, cleanup: cleanupQueryDir } = await dir({
|
||||
unsafeCleanup: true,
|
||||
});
|
||||
|
||||
const success = await setUpPack(this.cliServer, queryDir, language);
|
||||
if (!success) {
|
||||
await cleanupQueryDir();
|
||||
return;
|
||||
}
|
||||
|
||||
progress({
|
||||
message: "Opening editor...",
|
||||
step: 4,
|
||||
maxStep,
|
||||
});
|
||||
|
||||
const view = new ModelEditorView(
|
||||
this.app,
|
||||
this.modelingStore,
|
||||
this.editorViewTracker,
|
||||
this.databaseManager,
|
||||
this.cliServer,
|
||||
this.queryRunner,
|
||||
this.queryStorageDir,
|
||||
queryDir,
|
||||
db,
|
||||
modelFile,
|
||||
Mode.Application,
|
||||
);
|
||||
|
||||
this.modelingStore.onDbClosed(async (dbUri) => {
|
||||
if (dbUri === db.databaseUri.toString()) {
|
||||
await cleanupQueryDir();
|
||||
}
|
||||
});
|
||||
|
||||
this.push(view);
|
||||
this.push({
|
||||
dispose(): void {
|
||||
void cleanupQueryDir();
|
||||
},
|
||||
});
|
||||
|
||||
await view.openView();
|
||||
},
|
||||
{
|
||||
title: "Opening CodeQL Model Editor",
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { ResponsiveContainer as ResponsiveContainerComponent } from "../../view/common/ResponsiveContainer";
|
||||
|
||||
export default {
|
||||
title: "Responsive Container",
|
||||
component: ResponsiveContainerComponent,
|
||||
} as Meta<typeof ResponsiveContainerComponent>;
|
||||
|
||||
const Template: StoryFn<typeof ResponsiveContainerComponent> = (args) => (
|
||||
<ResponsiveContainerComponent>
|
||||
<span>Hello</span>
|
||||
</ResponsiveContainerComponent>
|
||||
);
|
||||
|
||||
export const ResponsiveContainer = Template.bind({});
|
||||
@@ -0,0 +1,16 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { NoMethodSelected as NoMethodSelectedComponent } from "../../view/method-modeling/NoMethodSelected";
|
||||
|
||||
export default {
|
||||
title: "Method Modeling/No Method Selected",
|
||||
component: NoMethodSelectedComponent,
|
||||
} as Meta<typeof NoMethodSelectedComponent>;
|
||||
|
||||
const Template: StoryFn<typeof NoMethodSelectedComponent> = () => (
|
||||
<NoMethodSelectedComponent />
|
||||
);
|
||||
|
||||
export const NoMethodSelected = Template.bind({});
|
||||
@@ -0,0 +1,16 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { Meta, StoryFn } from "@storybook/react";
|
||||
|
||||
import { NotInModelingMode as NotInModelingModeComponent } from "../../view/method-modeling/NotInModelingMode";
|
||||
|
||||
export default {
|
||||
title: "Method Modeling/Not In Modeling Mode",
|
||||
component: NotInModelingModeComponent,
|
||||
} as Meta<typeof NotInModelingModeComponent>;
|
||||
|
||||
const Template: StoryFn<typeof NotInModelingModeComponent> = () => (
|
||||
<NotInModelingModeComponent />
|
||||
);
|
||||
|
||||
export const NotInModelingMode = Template.bind({});
|
||||
15
extensions/ql-vscode/src/view/common/ResponsiveContainer.tsx
Normal file
15
extensions/ql-vscode/src/view/common/ResponsiveContainer.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { styled } from "styled-components";
|
||||
|
||||
export const ResponsiveContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
height: 100vh;
|
||||
|
||||
@media (min-height: 300px) {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,8 @@
|
||||
import * as React from "react";
|
||||
import { ResponsiveContainer } from "../common/ResponsiveContainer";
|
||||
|
||||
export const NoMethodSelected = () => {
|
||||
return (
|
||||
<ResponsiveContainer>Select an API or method to model</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as React from "react";
|
||||
import { useCallback } from "react";
|
||||
import { vscode } from "../vscode-api";
|
||||
import { styled } from "styled-components";
|
||||
import TextButton from "../common/TextButton";
|
||||
import { ResponsiveContainer } from "../common/ResponsiveContainer";
|
||||
|
||||
const Button = styled(TextButton)`
|
||||
margin-top: 0.2rem;
|
||||
`;
|
||||
|
||||
export const NotInModelingMode = () => {
|
||||
const handleClick = useCallback(() => {
|
||||
vscode.postMessage({
|
||||
t: "startModeling",
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ResponsiveContainer>
|
||||
<span>Not in modeling mode</span>
|
||||
<Button onClick={handleClick}>Start modeling</Button>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user