Skip to content

Commit

Permalink
Display Bicep build errors in the UI (#11372)
Browse files Browse the repository at this point in the history
<img width="1290" alt="image"
src="https://github.com/Azure/bicep/assets/38542602/2c2d04c0-94e2-47da-9a77-11b0a1582e80">

###### Microsoft Reviewers:
codeflow:open?pullrequest=#11372
  • Loading branch information
anthony-c-martin authored Jul 27, 2023
1 parent 3181738 commit 2499ee5
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 59 deletions.
18 changes: 9 additions & 9 deletions src/Bicep.LangServer/Handlers/GetDeploymentDataHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ namespace Bicep.LanguageServer.Handlers
{
[Method("bicep/getDeploymentData", Direction.ClientToServer)]
public record GetDeploymentDataRequest(TextDocumentIdentifier TextDocument)
: ITextDocumentIdentifierParams, IRequest<GetDeploymentDataResponse?>;
: ITextDocumentIdentifierParams, IRequest<GetDeploymentDataResponse>;

public record GetDeploymentDataResponse(string TemplateJson, string? ParametersJson);
public record GetDeploymentDataResponse(string? TemplateJson = null, string? ParametersJson = null, string? ErrorMessage = null);

public class GetDeploymentDataHandler : IJsonRpcRequestHandler<GetDeploymentDataRequest, GetDeploymentDataResponse?>
public class GetDeploymentDataHandler : IJsonRpcRequestHandler<GetDeploymentDataRequest, GetDeploymentDataResponse>
{
private readonly ILogger<BicepDocumentSymbolHandler> logger;
private readonly ICompilationManager compilationManager;
Expand All @@ -31,13 +31,13 @@ public GetDeploymentDataHandler(ILogger<BicepDocumentSymbolHandler> logger, ICom
this.compilationManager = compilationManager;
}

public async Task<GetDeploymentDataResponse?> Handle(GetDeploymentDataRequest request, CancellationToken cancellationToken)
public async Task<GetDeploymentDataResponse> Handle(GetDeploymentDataRequest request, CancellationToken cancellationToken)
{
await Task.Yield();

if (this.compilationManager.GetCompilation(request.TextDocument.Uri) is not {} context)
{
return null;
return new(ErrorMessage: $"Bicep compilation failed. An unexpected error occurred.");
}

var semanticModel = context.Compilation.GetEntrypointSemanticModel();
Expand All @@ -49,24 +49,24 @@ public GetDeploymentDataHandler(ILogger<BicepDocumentSymbolHandler> logger, ICom

if (paramsResult.Status == EmitStatus.Failed)
{
return null;
return new(ErrorMessage: $"Bicep compilation failed. The Bicep parameters file contains errors.");
}

paramsFile = paramsStringWriter.ToString();
if (!semanticModel.Root.TryGetBicepFileSemanticModelViaUsing(out semanticModel, out _))
{
return null;
return new(ErrorMessage: $"Bicep compilation failed. The Bicep parameters file contains errors.");
}
}

using var templateStringWriter = new StringWriter();
var result = new TemplateEmitter(semanticModel).Emit(templateStringWriter);
if (result.Status == EmitStatus.Failed)
{
return null;
return new(ErrorMessage: $"Bicep compilation failed. The Bicep file contains errors.");
}

return new(templateStringWriter.ToString(), paramsFile);
return new(TemplateJson: templateStringWriter.ToString(), ParametersJson: paramsFile);
}
}
}
5 changes: 3 additions & 2 deletions src/vscode-bicep/src/language/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,14 @@ export interface GetDeploymentDataRequest {
}

export interface GetDeploymentDataResponse {
templateJson: string;
templateJson?: string;
parametersJson?: string;
errorMessage?: string;
}

export const getDeploymentDataRequestType = new ProtocolRequestType<
GetDeploymentDataRequest,
GetDeploymentDataResponse | null,
GetDeploymentDataResponse,
never,
void,
void
Expand Down
18 changes: 15 additions & 3 deletions src/vscode-bicep/src/panes/deploy/app/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { FC } from "react";
import { FC, useState } from "react";
import { VSCodeButton, VSCodeDivider, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react";
import "./index.css";
import { ParamData } from "../../models";
Expand All @@ -15,13 +15,14 @@ import { DeploymentScopeInputView } from "./sections/DeploymentScopeInputView";
import { FormSection } from "./sections/FormSection";

export const App: FC = () => {
const messages = useMessageHandler();
const [errorMessage, setErrorMessage] = useState<string>();
const messages = useMessageHandler({ setErrorMessage });
const azure = useAzure({
scope: messages.scope,
acquireAccessToken: messages.acquireAccessToken,
templateMetadata: messages.templateMetadata,
parametersMetadata: messages.paramsMetadata,
showErrorDialog: messages.showErrorDialog
setErrorMessage
});

function setParamValue(key: string, data: ParamData) {
Expand Down Expand Up @@ -67,6 +68,17 @@ export const App: FC = () => {
onPickParametersFile={messages.pickParamsFile} />

<FormSection title="Actions">
{errorMessage && <div
style={{
color: "var(--vscode-statusBarItem-errorForeground)",
backgroundColor: "var(--vscode-statusBarItem-errorBackground)",
padding: '5px 10px',
borderRadius: '4px',
fontSize: '14px',
alignSelf: 'center'
}}>
{errorMessage}
</div>}
<div className="controls">
<VSCodeButton onClick={handleDeployClick} disabled={azureDisabled}>Deploy</VSCodeButton>
<VSCodeButton onClick={handleValidateClick} disabled={azureDisabled}>Validate</VSCodeButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const ParamInputBox: FC<ParamInputBoxProps> = (props) => {
<VSCodeTextArea
className="code-textarea-container"
resize="vertical"
value={JSON.stringify(value, null, 2)}
value={value ? JSON.stringify(value, null, 2) : ''}
onChange={e => handleValueChange(JSON.parse((e.currentTarget as HTMLInputElement).value))}
disabled={disabled}>
{name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface UseAzureProps {
templateMetadata?: TemplateMetadata;
parametersMetadata: ParametersMetadata;
acquireAccessToken: () => Promise<AccessToken>;
showErrorDialog: (callbackId: string, error: UntypedError) => void;
setErrorMessage: (message?: string) => void;
}

export function useAzure(props: UseAzureProps) {
Expand All @@ -33,7 +33,7 @@ export function useAzure(props: UseAzureProps) {
templateMetadata,
parametersMetadata,
acquireAccessToken,
showErrorDialog,
setErrorMessage,
} = props;
const deploymentName = "bicep-deploy";
const [operations, setOperations] = useState<DeploymentOperation[]>();
Expand Down Expand Up @@ -66,6 +66,7 @@ export function useAzure(props: UseAzureProps) {
}

try {
setErrorMessage(undefined);
clearState();
setRunning(true);

Expand All @@ -76,8 +77,8 @@ export function useAzure(props: UseAzureProps) {
const accessToken = await acquireAccessToken();
const armClient = getArmClient(scope, accessToken);
await operation(armClient, deployment);
} catch (e) {
showErrorDialog("doDeploymentOperation", e);
} catch (error) {
setErrorMessage(`Azure operation failed: ${error}`);
} finally {
setRunning(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
createPublishTelemetryMessage,
createReadyMessage,
createSaveStateMessage,
createShowUserErrorDialogMessage,
} from "../../../messages";
import { parseParametersJson, parseTemplateJson } from "../utils";
import {
Expand All @@ -30,7 +29,12 @@ let accessTokenResolver: {
reject: (error: UntypedError) => void;
};

export function useMessageHandler() {
export interface UseMessageHandlerProps {
setErrorMessage: (message?: string) => void;
}

export function useMessageHandler(props: UseMessageHandlerProps) {
const { setErrorMessage } = props;
const [persistedState, setPersistedState] = useState<DeployPaneState>();
const [templateMetadata, setTemplateMetadata] = useState<TemplateMetadata>();
const [paramsMetadata, setParamsMetadata] = useState<ParametersMetadata>({
Expand All @@ -42,20 +46,29 @@ export function useMessageHandler() {
const message = e.data;
switch (message.kind) {
case "DEPLOYMENT_DATA": {
if (!message.templateJson) {
setTemplateMetadata(undefined);
setErrorMessage(
message.errorMessage ??
"An error occurred building the deployment object.",
);
return;
}

const templateMetadata = parseTemplateJson(message.templateJson);
setTemplateMetadata(templateMetadata);

if (
templateMetadata.template["$schema"] !==
"https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"
) {
showErrorDialog(
"handleMessageEvent",
setTemplateMetadata(undefined);
setErrorMessage(
"The deployment pane currently only supports resourceGroup-scoped Bicep files.",
);
return;
}

setTemplateMetadata(templateMetadata);
if (message.parametersJson) {
setParamsMetadata({
sourceFilePath: message.documentPath.endsWith(".bicep")
Expand All @@ -64,6 +77,7 @@ export function useMessageHandler() {
parameters: parseParametersJson(message.parametersJson),
});
}
setErrorMessage(undefined);
return;
}
case "GET_STATE_RESULT": {
Expand Down Expand Up @@ -116,10 +130,6 @@ export function useMessageHandler() {
vscode.postMessage(createGetDeploymentScopeMessage());
}

function showErrorDialog(callbackId: string, error: UntypedError) {
vscode.postMessage(createShowUserErrorDialogMessage(callbackId, error));
}

function publishTelemetry(
eventName: string,
properties: TelemetryProperties,
Expand All @@ -137,7 +147,6 @@ export function useMessageHandler() {
}

return {
showErrorDialog,
pickParamsFile,
paramsMetadata,
setParamsMetadata,
Expand Down
25 changes: 5 additions & 20 deletions src/vscode-bicep/src/panes/deploy/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,22 @@ export type DeploymentDataMessage = MessageWithPayload<
"DEPLOYMENT_DATA",
{
documentPath: string;
templateJson: string;
templateJson?: string;
parametersJson?: string;
errorMessage?: string;
}
>;
export function createDeploymentDataMessage(
documentPath: string,
templateJson: string,
templateJson?: string,
parametersJson?: string,
errorMessage?: string,
): DeploymentDataMessage {
return createMessageWithPayload("DEPLOYMENT_DATA", {
documentPath,
templateJson,
parametersJson,
errorMessage,
});
}

Expand Down Expand Up @@ -140,23 +143,6 @@ export function createGetDeploymentScopeResultMessage(
});
}

export type ShowUserErrorDialogMessage = MessageWithPayload<
"SHOW_USER_ERROR_DIALOG",
{
callbackId: string;
error: UntypedError;
}
>;
export function createShowUserErrorDialogMessage(
callbackId: string,
error: UntypedError,
): ShowUserErrorDialogMessage {
return createMessageWithPayload("SHOW_USER_ERROR_DIALOG", {
callbackId,
error,
});
}

export type PublishTelemetryMessage = MessageWithPayload<
"PUBLISH_TELEMETRY",
{
Expand Down Expand Up @@ -188,7 +174,6 @@ export type ViewMessage =
| PickParamsFileMessage
| GetAccessTokenMessage
| GetDeploymentScopeMessage
| ShowUserErrorDialogMessage
| PublishTelemetryMessage;

function createSimpleMessage<T>(kind: T): SimpleMessage<T> {
Expand Down
11 changes: 1 addition & 10 deletions src/vscode-bicep/src/panes/deploy/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import fse from "fs-extra";
import path from "path";
import crypto from "crypto";
import { LanguageClient } from "vscode-languageclient/node";

import {
createDeploymentDataMessage,
createGetAccessTokenResultMessage,
Expand All @@ -25,7 +24,6 @@ import {
IActionContext,
} from "@microsoft/vscode-azext-utils";
import { GlobalStateKeys } from "../../globalState";
import { raiseErrorWithoutTelemetry } from "../../utils/telemetry";
import { DeployPaneState } from "./models";

export class DeployPaneView extends Disposable {
Expand Down Expand Up @@ -180,16 +178,13 @@ export class DeployPaneView extends Disposable {
return;
}

if (!deploymentData) {
return;
}

try {
await this.webviewPanel.webview.postMessage(
createDeploymentDataMessage(
this.documentUri.fsPath,
deploymentData.templateJson,
deploymentData.parametersJson,
deploymentData.errorMessage,
),
);
} catch (error) {
Expand Down Expand Up @@ -289,10 +284,6 @@ export class DeployPaneView extends Disposable {

return;
}
case "SHOW_USER_ERROR_DIALOG": {
await raiseErrorWithoutTelemetry(message.callbackId, message.error);
return;
}
case "PUBLISH_TELEMETRY": {
callWithTelemetryAndErrorHandlingSync(
message.eventName,
Expand Down

0 comments on commit 2499ee5

Please sign in to comment.