Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions extensions/mssql/l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,8 @@
},
"Chat": "Chat",
"Open in GitHub Copilot Chat": "Open in GitHub Copilot Chat",
"Ask GitHub Copilot to Fix": "Ask GitHub Copilot to Fix",
"Open GitHub Copilot Chat to help fix these errors": "Open GitHub Copilot Chat to help fix these errors",
"Design Schemas with GitHub Copilot": "Design Schemas with GitHub Copilot",
"Ask questions or propose schema changes in chat, and GitHub Copilot updates the schema instantly in the diagram.": "Ask questions or propose schema changes in chat, and GitHub Copilot updates the schema instantly in the diagram.",
"Build APIs with GitHub Copilot": "Build APIs with GitHub Copilot",
Expand Down
10 changes: 6 additions & 4 deletions extensions/mssql/src/controllers/mainController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,7 @@ export default class MainController implements vscode.Disposable {
private async openCopilotChatFromUi(args?: CopilotChat.OpenFromUiArgs): Promise<void> {
const scenario = args?.scenario ?? "schemaDesigner";
const entryPoint = args?.entryPoint ?? "schemaDesignerToolbar";
const promptOverride = args?.prompt?.trim();
const sendCopilotChatEntryTelemetry = (
success: boolean,
reason?: "noActiveDesigner" | "chatCommandMissing",
Expand Down Expand Up @@ -967,10 +968,11 @@ export default class MainController implements vscode.Disposable {
return;
}

await vscode.commands.executeCommand(
chatCommand,
this.getCopilotChatPromptForScenario(scenario),
);
const promptToUse =
promptOverride && promptOverride.length > 0
? promptOverride
: this.getCopilotChatPromptForScenario(scenario);
await vscode.commands.executeCommand(chatCommand, promptToUse);
sendCopilotChatEntryTelemetry(true);
}

Expand Down
4 changes: 4 additions & 0 deletions extensions/mssql/src/reactviews/common/locConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,10 @@ export class LocConstants {
publishChanges: l10n.t("Apply Changes"),
openCopilotForSchemaDesigner: l10n.t("Chat"),
openCopilotForSchemaDesignerTooltip: l10n.t("Open in GitHub Copilot Chat"),
askGithubCopilotToFix: l10n.t("Ask GitHub Copilot to Fix"),
askGithubCopilotToFixTooltip: l10n.t(
"Open GitHub Copilot Chat to help fix these errors",
),
schemaDesignerCopilotDiscoveryTitle: l10n.t("Design Schemas with GitHub Copilot"),
schemaDesignerCopilotDiscoveryBody: l10n.t(
"Ask questions or propose schema changes in chat, and GitHub Copilot updates the schema instantly in the diagram.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,17 @@ import Markdown from "react-markdown";
import { SchemaDesigner } from "../../../../sharedInterfaces/schemaDesigner";
import { useMarkdownStyles } from "../../../common/styles";
import { useSchemaDesignerChangeContext } from "../definition/changes/schemaDesignerChangeContext";
import { useSchemaDesignerSelector } from "../schemaDesignerSelector";
import { CopilotChat } from "../../../../sharedInterfaces/copilotChat";
import { ExecuteCommandRequest } from "../../../../sharedInterfaces/webview";
import { GithubCopilot16Regular } from "../../../common/icons/fluentIcons";
import {
schemaDesignerPublishErrorDetailsLabel,
schemaDesignerPublishErrorFallbackDetails,
schemaDesignerPublishErrorPrompt,
} from "./publishChangesDialogPrompts";

enum PublishDialogStages {
export enum PublishDialogStages {
NotStarted = "notStarted",
ReportLoading = "reportLoading",
ReportError = "reportError",
Expand All @@ -47,6 +56,45 @@ type PublishChangesDialogState = {
currentStage: PublishDialogStages;
};

export function buildSchemaDesignerPublishErrorPrompt(errorString: string): string {
const errorDetails = errorString.trim() || schemaDesignerPublishErrorFallbackDetails;
return `${schemaDesignerPublishErrorPrompt}

${schemaDesignerPublishErrorDetailsLabel}
\`\`\`
${errorDetails}
\`\`\``;
}

export function isReportOrPublishErrorStage(currentStage: PublishDialogStages): boolean {
return (
currentStage === PublishDialogStages.ReportError ||
currentStage === PublishDialogStages.PublishError
);
}

export function getReportOrPublishErrorForStage(
currentStage: PublishDialogStages,
reportError: string | undefined,
publishError: string | undefined,
): string {
if (currentStage === PublishDialogStages.ReportError) {
return reportError ?? "";
}
if (currentStage === PublishDialogStages.PublishError) {
return publishError ?? "";
}
return "";
}

export function shouldShowGithubCopilotFixButton(
currentStage: PublishDialogStages,
isCopilotChatInstalled: boolean,
isDabEnabled: boolean,
): boolean {
return isReportOrPublishErrorStage(currentStage) && isCopilotChatInstalled && isDabEnabled;
}

const useStyles = makeStyles({
errorSection: {
marginBottom: "15px",
Expand All @@ -67,6 +115,8 @@ export function PublishChangesDialogButton() {
const markdownClasses = useMarkdownStyles();
const context = useContext(SchemaDesignerContext);
const changeContext = useSchemaDesignerChangeContext();
const isCopilotChatInstalled =
useSchemaDesignerSelector((s) => s?.isCopilotChatInstalled) ?? false;
const [open, setOpen] = useState(false);
const [publishButtonDisabled, setPublishButtonDisabled] = useState(false);
const hasSchemaChanges = changeContext.schemaChangesCount > 0;
Expand Down Expand Up @@ -372,6 +422,42 @@ export function PublishChangesDialogButton() {
);
};

const isGithubCopilotFixButtonVisible = () => {
return shouldShowGithubCopilotFixButton(
state.currentStage,
isCopilotChatInstalled,
context.isDabEnabled(),
);
};

const getCurrentError = () => {
return getReportOrPublishErrorForStage(
state.currentStage,
state.reportError,
state.publishError,
);
};

const openGithubCopilotToFixError = async () => {
const prompt = buildSchemaDesignerPublishErrorPrompt(getCurrentError());
setOpen(false);
setState({
...state,
isConfirmationChecked: false,
});

await context.extensionRpc.sendRequest(ExecuteCommandRequest.type, {
command: CopilotChat.openFromUiCommand,
args: [
{
scenario: "schemaDesigner",
entryPoint: "schemaDesignerPublishDialogError",
prompt,
},
],
});
};

const footerButtons = () => {
return (
<>
Expand Down Expand Up @@ -428,6 +514,21 @@ export function PublishChangesDialogButton() {
</Button>
</DialogTrigger>
)}
{isGithubCopilotFixButtonVisible() && (
<Tooltip
content={locConstants.schemaDesigner.askGithubCopilotToFixTooltip}
relationship="description">
<Button
appearance="secondary"
icon={<GithubCopilot16Regular />}
title={locConstants.schemaDesigner.askGithubCopilotToFixTooltip}
onClick={async () => {
await openGithubCopilotToFixError();
}}>
{locConstants.schemaDesigner.askGithubCopilotToFix}
</Button>
</Tooltip>
)}
{state.currentStage !== PublishDialogStages.PublishLoading && (
<DialogTrigger disableButtonEnhancement>
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

export const schemaDesignerPublishErrorPrompt = `Use mssql_schema_designer for the active schema designer.
Diagnose the root cause and propose schema fixes.
Ask for my confirmation before applying any edits.`;

export const schemaDesignerPublishErrorDetailsLabel = "Error details:";
export const schemaDesignerPublishErrorFallbackDetails =
"No additional error details were provided.";
6 changes: 5 additions & 1 deletion extensions/mssql/src/sharedInterfaces/copilotChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ export namespace CopilotChat {
const discoveryDismissedStateKeyPrefix = "mssql.copilotChatDiscoveryDismissed";

export type Scenario = "schemaDesigner" | "dab";
export type EntryPoint = "schemaDesignerToolbar" | "dabToolbar";
export type EntryPoint =
| "schemaDesignerToolbar"
| "schemaDesignerPublishDialogError"
| "dabToolbar";

export interface OpenFromUiArgs {
scenario: Scenario;
entryPoint: EntryPoint;
prompt?: string;
}

export type DiscoveryDismissedState = Partial<Record<Scenario, boolean>>;
Expand Down
26 changes: 26 additions & 0 deletions extensions/mssql/test/unit/mainController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -846,5 +846,31 @@ suite("MainController Tests", function () {
},
);
});

test("opens chat with prompt override when provided", async () => {
const { isolatedController, isolatedVscodeWrapper } = createIsolatedController();
const showErrorMessageStub = isolatedVscodeWrapper.showErrorMessage as sinon.SinonStub;
const executeCommandStub = sandbox
.stub(vscode.commands, "executeCommand")
.resolves(undefined);
sandbox.stub(SchemaDesignerWebviewManager, "getInstance").returns({
getActiveDesigner: sandbox.stub().returns({}),
} as any);
sandbox
.stub(isolatedController as any, "findChatOpenAgentCommand")
.resolves(Constants.vscodeWorkbenchChatOpenAgent);

await (isolatedController as any).openCopilotChatFromUi({
scenario: "schemaDesigner",
entryPoint: "schemaDesignerPublishDialogError",
prompt: "custom GHCP fix prompt",
});

expect(showErrorMessageStub).to.not.have.been.called;
expect(executeCommandStub).to.have.been.calledWith(
Constants.vscodeWorkbenchChatOpenAgent,
"custom GHCP fix prompt",
);
});
});
});
Loading
Loading