Skip to content

Commit a9e8034

Browse files
authored
Add GHCP fix action for publish dialog errors (#21455)
* Add GHCP fix action for publish dialog errors * Adjust GHCP button accessibility semantics
1 parent 9168d98 commit a9e8034

9 files changed

Lines changed: 495 additions & 6 deletions

File tree

extensions/mssql/l10n/bundle.l10n.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,8 @@
562562
},
563563
"Chat": "Chat",
564564
"Open in GitHub Copilot Chat": "Open in GitHub Copilot Chat",
565+
"Ask GitHub Copilot to Fix": "Ask GitHub Copilot to Fix",
566+
"Open GitHub Copilot Chat to help fix these errors": "Open GitHub Copilot Chat to help fix these errors",
565567
"Design Schemas with GitHub Copilot": "Design Schemas with GitHub Copilot",
566568
"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.",
567569
"Build APIs with GitHub Copilot": "Build APIs with GitHub Copilot",

extensions/mssql/src/controllers/mainController.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,7 @@ export default class MainController implements vscode.Disposable {
937937
private async openCopilotChatFromUi(args?: CopilotChat.OpenFromUiArgs): Promise<void> {
938938
const scenario = args?.scenario ?? "schemaDesigner";
939939
const entryPoint = args?.entryPoint ?? "schemaDesignerToolbar";
940+
const promptOverride = args?.prompt?.trim();
940941
const sendCopilotChatEntryTelemetry = (
941942
success: boolean,
942943
reason?: "noActiveDesigner" | "chatCommandMissing",
@@ -967,10 +968,11 @@ export default class MainController implements vscode.Disposable {
967968
return;
968969
}
969970

970-
await vscode.commands.executeCommand(
971-
chatCommand,
972-
this.getCopilotChatPromptForScenario(scenario),
973-
);
971+
const promptToUse =
972+
promptOverride && promptOverride.length > 0
973+
? promptOverride
974+
: this.getCopilotChatPromptForScenario(scenario);
975+
await vscode.commands.executeCommand(chatCommand, promptToUse);
974976
sendCopilotChatEntryTelemetry(true);
975977
}
976978

extensions/mssql/src/reactviews/common/locConstants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,10 @@ export class LocConstants {
810810
publishChanges: l10n.t("Apply Changes"),
811811
openCopilotForSchemaDesigner: l10n.t("Chat"),
812812
openCopilotForSchemaDesignerTooltip: l10n.t("Open in GitHub Copilot Chat"),
813+
askGithubCopilotToFix: l10n.t("Ask GitHub Copilot to Fix"),
814+
askGithubCopilotToFixTooltip: l10n.t(
815+
"Open GitHub Copilot Chat to help fix these errors",
816+
),
813817
schemaDesignerCopilotDiscoveryTitle: l10n.t("Design Schemas with GitHub Copilot"),
814818
schemaDesignerCopilotDiscoveryBody: l10n.t(
815819
"Ask questions or propose schema changes in chat, and GitHub Copilot updates the schema instantly in the diagram.",

extensions/mssql/src/reactviews/pages/SchemaDesigner/toolbar/publishChangesDialogButton.tsx

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,17 @@ import Markdown from "react-markdown";
2626
import { SchemaDesigner } from "../../../../sharedInterfaces/schemaDesigner";
2727
import { useMarkdownStyles } from "../../../common/styles";
2828
import { useSchemaDesignerChangeContext } from "../definition/changes/schemaDesignerChangeContext";
29+
import { useSchemaDesignerSelector } from "../schemaDesignerSelector";
30+
import { CopilotChat } from "../../../../sharedInterfaces/copilotChat";
31+
import { ExecuteCommandRequest } from "../../../../sharedInterfaces/webview";
32+
import { GithubCopilot16Regular } from "../../../common/icons/fluentIcons";
33+
import {
34+
schemaDesignerPublishErrorDetailsLabel,
35+
schemaDesignerPublishErrorFallbackDetails,
36+
schemaDesignerPublishErrorPrompt,
37+
} from "./publishChangesDialogPrompts";
2938

30-
enum PublishDialogStages {
39+
export enum PublishDialogStages {
3140
NotStarted = "notStarted",
3241
ReportLoading = "reportLoading",
3342
ReportError = "reportError",
@@ -47,6 +56,45 @@ type PublishChangesDialogState = {
4756
currentStage: PublishDialogStages;
4857
};
4958

59+
export function buildSchemaDesignerPublishErrorPrompt(errorString: string): string {
60+
const errorDetails = errorString.trim() || schemaDesignerPublishErrorFallbackDetails;
61+
return `${schemaDesignerPublishErrorPrompt}
62+
63+
${schemaDesignerPublishErrorDetailsLabel}
64+
\`\`\`
65+
${errorDetails}
66+
\`\`\``;
67+
}
68+
69+
export function isReportOrPublishErrorStage(currentStage: PublishDialogStages): boolean {
70+
return (
71+
currentStage === PublishDialogStages.ReportError ||
72+
currentStage === PublishDialogStages.PublishError
73+
);
74+
}
75+
76+
export function getReportOrPublishErrorForStage(
77+
currentStage: PublishDialogStages,
78+
reportError: string | undefined,
79+
publishError: string | undefined,
80+
): string {
81+
if (currentStage === PublishDialogStages.ReportError) {
82+
return reportError ?? "";
83+
}
84+
if (currentStage === PublishDialogStages.PublishError) {
85+
return publishError ?? "";
86+
}
87+
return "";
88+
}
89+
90+
export function shouldShowGithubCopilotFixButton(
91+
currentStage: PublishDialogStages,
92+
isCopilotChatInstalled: boolean,
93+
isDabEnabled: boolean,
94+
): boolean {
95+
return isReportOrPublishErrorStage(currentStage) && isCopilotChatInstalled && isDabEnabled;
96+
}
97+
5098
const useStyles = makeStyles({
5199
errorSection: {
52100
marginBottom: "15px",
@@ -67,6 +115,8 @@ export function PublishChangesDialogButton() {
67115
const markdownClasses = useMarkdownStyles();
68116
const context = useContext(SchemaDesignerContext);
69117
const changeContext = useSchemaDesignerChangeContext();
118+
const isCopilotChatInstalled =
119+
useSchemaDesignerSelector((s) => s?.isCopilotChatInstalled) ?? false;
70120
const [open, setOpen] = useState(false);
71121
const [publishButtonDisabled, setPublishButtonDisabled] = useState(false);
72122
const hasSchemaChanges = changeContext.schemaChangesCount > 0;
@@ -372,6 +422,42 @@ export function PublishChangesDialogButton() {
372422
);
373423
};
374424

425+
const isGithubCopilotFixButtonVisible = () => {
426+
return shouldShowGithubCopilotFixButton(
427+
state.currentStage,
428+
isCopilotChatInstalled,
429+
context.isDabEnabled(),
430+
);
431+
};
432+
433+
const getCurrentError = () => {
434+
return getReportOrPublishErrorForStage(
435+
state.currentStage,
436+
state.reportError,
437+
state.publishError,
438+
);
439+
};
440+
441+
const openGithubCopilotToFixError = async () => {
442+
const prompt = buildSchemaDesignerPublishErrorPrompt(getCurrentError());
443+
setOpen(false);
444+
setState({
445+
...state,
446+
isConfirmationChecked: false,
447+
});
448+
449+
await context.extensionRpc.sendRequest(ExecuteCommandRequest.type, {
450+
command: CopilotChat.openFromUiCommand,
451+
args: [
452+
{
453+
scenario: "schemaDesigner",
454+
entryPoint: "schemaDesignerPublishDialogError",
455+
prompt,
456+
},
457+
],
458+
});
459+
};
460+
375461
const footerButtons = () => {
376462
return (
377463
<>
@@ -428,6 +514,21 @@ export function PublishChangesDialogButton() {
428514
</Button>
429515
</DialogTrigger>
430516
)}
517+
{isGithubCopilotFixButtonVisible() && (
518+
<Tooltip
519+
content={locConstants.schemaDesigner.askGithubCopilotToFixTooltip}
520+
relationship="description">
521+
<Button
522+
appearance="secondary"
523+
icon={<GithubCopilot16Regular />}
524+
title={locConstants.schemaDesigner.askGithubCopilotToFixTooltip}
525+
onClick={async () => {
526+
await openGithubCopilotToFixError();
527+
}}>
528+
{locConstants.schemaDesigner.askGithubCopilotToFix}
529+
</Button>
530+
</Tooltip>
531+
)}
431532
{state.currentStage !== PublishDialogStages.PublishLoading && (
432533
<DialogTrigger disableButtonEnhancement>
433534
<Button
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
export const schemaDesignerPublishErrorPrompt = `Use mssql_schema_designer for the active schema designer.
7+
Diagnose the root cause and propose schema fixes.
8+
Ask for my confirmation before applying any edits.`;
9+
10+
export const schemaDesignerPublishErrorDetailsLabel = "Error details:";
11+
export const schemaDesignerPublishErrorFallbackDetails =
12+
"No additional error details were provided.";

extensions/mssql/src/sharedInterfaces/copilotChat.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ export namespace CopilotChat {
88
const discoveryDismissedStateKeyPrefix = "mssql.copilotChatDiscoveryDismissed";
99

1010
export type Scenario = "schemaDesigner" | "dab";
11-
export type EntryPoint = "schemaDesignerToolbar" | "dabToolbar";
11+
export type EntryPoint =
12+
| "schemaDesignerToolbar"
13+
| "schemaDesignerPublishDialogError"
14+
| "dabToolbar";
1215

1316
export interface OpenFromUiArgs {
1417
scenario: Scenario;
1518
entryPoint: EntryPoint;
19+
prompt?: string;
1620
}
1721

1822
export type DiscoveryDismissedState = Partial<Record<Scenario, boolean>>;

extensions/mssql/test/unit/mainController.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,5 +846,31 @@ suite("MainController Tests", function () {
846846
},
847847
);
848848
});
849+
850+
test("opens chat with prompt override when provided", async () => {
851+
const { isolatedController, isolatedVscodeWrapper } = createIsolatedController();
852+
const showErrorMessageStub = isolatedVscodeWrapper.showErrorMessage as sinon.SinonStub;
853+
const executeCommandStub = sandbox
854+
.stub(vscode.commands, "executeCommand")
855+
.resolves(undefined);
856+
sandbox.stub(SchemaDesignerWebviewManager, "getInstance").returns({
857+
getActiveDesigner: sandbox.stub().returns({}),
858+
} as any);
859+
sandbox
860+
.stub(isolatedController as any, "findChatOpenAgentCommand")
861+
.resolves(Constants.vscodeWorkbenchChatOpenAgent);
862+
863+
await (isolatedController as any).openCopilotChatFromUi({
864+
scenario: "schemaDesigner",
865+
entryPoint: "schemaDesignerPublishDialogError",
866+
prompt: "custom GHCP fix prompt",
867+
});
868+
869+
expect(showErrorMessageStub).to.not.have.been.called;
870+
expect(executeCommandStub).to.have.been.calledWith(
871+
Constants.vscodeWorkbenchChatOpenAgent,
872+
"custom GHCP fix prompt",
873+
);
874+
});
849875
});
850876
});

0 commit comments

Comments
 (0)