Skip to content

Commit

Permalink
Add empty states for modeling panel (#2914)
Browse files Browse the repository at this point in the history
  • Loading branch information
charisk authored Oct 6, 2023
1 parent 1806108 commit db3d242
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 111 deletions.
1 change: 1 addition & 0 deletions extensions/ql-vscode/src/common/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ export type PackagingCommands = {

export type ModelEditorCommands = {
"codeQL.openModelEditor": () => Promise<void>;
"codeQL.openModelEditorFromModelingPanel": () => Promise<void>;
"codeQLModelEditor.jumpToUsageLocation": (
method: Method,
usage: Usage,
Expand Down
7 changes: 6 additions & 1 deletion extensions/ql-vscode/src/common/interface-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,10 +610,15 @@ interface RevealInEditorMessage {
method: Method;
}

interface StartModelingMessage {
t: "startModeling";
}

export type FromMethodModelingMessage =
| CommonFromViewMessages
| SetModeledMethodMessage
| RevealInEditorMessage;
| RevealInEditorMessage
| StartModelingMessage;

interface SetMethodMessage {
t: "setMethod";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export abstract class AbstractWebviewViewProvider<
private disposables: Disposable[] = [];

constructor(
private readonly app: App,
protected readonly app: App,
private readonly webviewKind: WebviewKind,
) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
224 changes: 115 additions & 109 deletions extensions/ql-vscode/src/model-editor/model-editor-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
},
);
}
}
}
Original file line number Diff line number Diff line change
@@ -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({});
Original file line number Diff line number Diff line change
@@ -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({});
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions extensions/ql-vscode/src/view/common/ResponsiveContainer.tsx
Original file line number Diff line number Diff line change
@@ -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;
}
`;
Original file line number Diff line number Diff line change
@@ -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>
);
};
Original file line number Diff line number Diff line change
@@ -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>
);
};

0 comments on commit db3d242

Please sign in to comment.