Skip to content

Commit

Permalink
Display an edit configuration button on deployment failure message fo…
Browse files Browse the repository at this point in the history
…r invalid toml
  • Loading branch information
sagerb committed Oct 15, 2024
1 parent 61a3621 commit 4eee8ec
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 7 deletions.
115 changes: 115 additions & 0 deletions extensions/vscode/src/utils/errorTypes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import {
errUnknownTOMLKeyMessage,
isErrInvalidConfigFile,
resolveAgentJsonErrorMsg,
ErrTomlUnknownError,
isErrTomlUnknownError,
errTomlUnknownErrorMessage,
ErrTOMLValidationError,
isErrTOMLValidationError,
errTOMLValidationErrorMessage,
} from "./errorTypes";

const mkAxiosJsonErr = (data: Record<PropertyKey, any>) => {
Expand Down Expand Up @@ -158,6 +164,115 @@ describe("ErrUnknownTOMLKey", () => {
});
});

describe("ErrTOMLValidationError", () => {
test("isErrUnknownTOMLKey", () => {
let result = isErrUnknownTOMLKey(
mkAxiosJsonErr({
code: "unknownTOMLKey",
}),
);

expect(result).toBe(true);

result = isErrUnknownTOMLKey(
mkAxiosJsonErr({
code: "bricks_raining",
}),
);

expect(result).toBe(false);
});

test("errUnknownTOMLKeyMessage", () => {
const err = mkAxiosJsonErr({
code: "unknownTOMLKey",
details: {
filename: "/directory/configuration-lkdg.toml",
line: 7,
column: 1,
key: "shortcut_key",
},
});

const msg = errUnknownTOMLKeyMessage(
err as axiosErrorWithJson<ErrUnknownTOMLKey>,
);
expect(msg).toBe(`The Configuration has a schema error on line 7`);
});
});

describe("ErrTomlUnknownError", () => {
test("isErrTomlUnknownError", () => {
let result = isErrTomlUnknownError(
mkAxiosJsonErr({
code: "tomlUnknownError",
}),
);

expect(result).toBe(true);

result = isErrTomlUnknownError(
mkAxiosJsonErr({
code: "bricks_raining",
}),
);

expect(result).toBe(false);
});

test("errTomlUnknownErrorMessage", () => {
const err = mkAxiosJsonErr({
code: "tomlUnknownError",
details: {
filename: "config.toml",
problem: "problems...",
},
});

const msg = errTomlUnknownErrorMessage(
err as axiosErrorWithJson<ErrTomlUnknownError>,
);
expect(msg).toBe(`The Configuration has a schema error`);
});
});

describe("ErrTOMLValidationError", () => {
test("isErrTOMLValidationError", () => {
let result = isErrTOMLValidationError(
mkAxiosJsonErr({
code: "tomlValidationError",
}),
);

expect(result).toBe(true);

result = isErrTOMLValidationError(
mkAxiosJsonErr({
code: "bricks_raining",
}),
);

expect(result).toBe(false);
});

test("errTOMLValidationErrorMessage", () => {
const err = mkAxiosJsonErr({
code: "tomlValidationError",
details: {
filename: "/directory/configuration-lkdg.toml",
line: 7,
column: 1,
key: "shortcut_key",
},
});

const msg = errTOMLValidationErrorMessage(
err as axiosErrorWithJson<ErrTOMLValidationError>,
);
expect(msg).toBe(`The Configuration has a schema error`);
});
});

describe("ErrInvalidConfigFiles", () => {
test("isErrInvalidConfigFile", () => {
let result = isErrInvalidConfigFile(
Expand Down
46 changes: 45 additions & 1 deletion extensions/vscode/src/utils/errorTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export type ErrorCode =
| "deployFailed"
| "renvlockPackagesReadingError"
| "requirementsFileReadingError"
| "deployedContentNotRunning";
| "deployedContentNotRunning"
| "tomlValidationError"
| "tomlUnknownError";

export type axiosErrorWithJson<T = { code: ErrorCode; details: unknown }> =
AxiosError & {
Expand Down Expand Up @@ -69,6 +71,25 @@ export type ErrResourceNotFound = MkErrorDataType<
export const isErrResourceNotFound =
mkErrorTypeGuard<ErrResourceNotFound>("resourceNotFound");

// Invalid TOML file(s)
export type ErrTOMLValidationError = MkErrorDataType<
"tomlValidationError",
{
filename: string;
message: string;
key: string;
problem: string;
schemaReference: string;
}
>;
export const isErrTOMLValidationError =
mkErrorTypeGuard<ErrTOMLValidationError>("tomlValidationError");
export const errTOMLValidationErrorMessage = (
_: axiosErrorWithJson<ErrTOMLValidationError>,
) => {
return `The Configuration has a schema error`;
};

// Invalid TOML file(s)
export type ErrInvalidTOMLFile = MkErrorDataType<
"invalidTOML",
Expand Down Expand Up @@ -104,6 +125,22 @@ export const errUnknownTOMLKeyMessage = (
return `The Configuration has a schema error on line ${err.response.data.details.line}`;
};

// Unknown error within a TOML file
export type ErrTomlUnknownError = MkErrorDataType<
"tomlUnknownError",
{
filename: string;
problem: string;
}
>;
export const isErrTomlUnknownError =
mkErrorTypeGuard<ErrTomlUnknownError>("tomlUnknownError");
export const errTomlUnknownErrorMessage = (
_: axiosErrorWithJson<ErrTomlUnknownError>,
) => {
return `The Configuration has a schema error`;
};

// Invalid configuration file(s)
export type ErrInvalidConfigFiles = MkErrorDataType<
"invalidConfigFile",
Expand All @@ -123,5 +160,12 @@ export function resolveAgentJsonErrorMsg(err: axiosErrorWithJson) {
return errInvalidTOMLMessage(err);
}

if (isErrTOMLValidationError(err)) {
return errTOMLValidationErrorMessage(err);
}

if (isErrTomlUnknownError(err)) {
return errTomlUnknownErrorMessage(err);
}
return errUnknownMessage(err as axiosErrorWithJson<ErrUnknown>);
}
6 changes: 4 additions & 2 deletions extensions/vscode/src/views/homeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import { throttleWithLastPending } from "src/utils/throttle";
import { showAssociateGUID } from "src/actions/showAssociateGUID";
import { extensionSettings } from "src/extension";
import { openFileInEditor } from "src/commands";
import { showImmediateDeploymentFailureMessage } from "./publishFailures";

enum HomeViewInitialized {
initialized = "initialized",
Expand Down Expand Up @@ -267,8 +268,9 @@ export class HomeViewProvider implements WebviewViewProvider, Disposable {
);
deployProject(response.data.localId, this.stream);
} catch (error: unknown) {
const summary = getSummaryStringFromError("homeView, deploy", error);
window.showErrorMessage(`Failed to deploy. ${summary}`);
// Most failures will occur on the event stream. These are the ones which
// are immediately rejected as part of the API request to initiate deployment.
showImmediateDeploymentFailureMessage(error);
}
}

Expand Down
59 changes: 59 additions & 0 deletions extensions/vscode/src/views/publishFailures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (C) 2024 by Posit Software, PBC.

import { window } from "vscode";
import { openFileInEditor } from "src/commands";
import {
isErrInvalidConfigFile,
isErrInvalidTOMLFile,
isErrTomlUnknownError,
isErrTOMLValidationError,
isErrUnknownTOMLKey,
} from "src/utils/errorTypes";
import { getSummaryStringFromError } from "src/utils/errors";

// Handler for deployment failures, but intended to only handled the
// errors reported back from the deployment API request. Most deployment
// failures will be returned and handled by the event stream processing
// once the agent has kicked off the anonymous go function which walks through
// the deployment process.
export const showImmediateDeploymentFailureMessage = async (error: unknown) => {
if (
isErrTomlUnknownError(error) ||
isErrUnknownTOMLKey(error) ||
isErrInvalidTOMLFile(error) ||
isErrTOMLValidationError(error) ||
isErrInvalidConfigFile(error)
) {
const editButtonStr = "Edit Configuration";
const options = [editButtonStr];
const summary = getSummaryStringFromError("homeView, deploy", error);
const selection = await window.showErrorMessage(
`Failed to deploy. ${summary}`,
...options,
);
// will not support line and column either.
if (selection === editButtonStr) {
if (
isErrTOMLValidationError(error) ||
isErrTomlUnknownError(error) ||
isErrInvalidConfigFile(error)
) {
openFileInEditor(error.response.data.details.filename);
return;
}
openFileInEditor(error.response.data.details.filename, {
selection: {
start: {
line: error.response.data.details.line - 1,
character: error.response.data.details.column - 1,
},
},
});
}
return;
}

// Default handling for deployment failures we are not handling in a particular way
const summary = getSummaryStringFromError("homeView, deploy", error);
return window.showErrorMessage(`Failed to deploy . ${summary}`);
};
2 changes: 1 addition & 1 deletion internal/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (v *Validator[T]) ValidateContent(data any) error {
if ok {
// Return all causes in the Data field of a single error.
e := toTomlValidationError(validationErr)
return types.NewAgentError(tomlValidationErrorCode, e, nil)
return types.NewAgentError(tomlValidationErrorCode, e, e)
} else {
return err
}
Expand Down
21 changes: 20 additions & 1 deletion internal/services/api/post_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/gorilla/mux"
"github.com/posit-dev/publisher/internal/accounts"
"github.com/posit-dev/publisher/internal/config"
"github.com/posit-dev/publisher/internal/events"
"github.com/posit-dev/publisher/internal/logging"
"github.com/posit-dev/publisher/internal/publish"
Expand Down Expand Up @@ -58,13 +59,14 @@ func PostDeploymentHandlerFunc(
return
}
newState, err := stateFactory(projectDir, b.AccountName, b.ConfigName, name, "", accountList, b.Secrets, b.Insecure)
log.Debug("New account derived state created", "account", b.AccountName, "config", b.ConfigName)
if err != nil {
if errors.Is(err, accounts.ErrAccountNotFound) {
log.Error("Deployment initialization failure - account not found", "error", err.Error())
NotFound(w, log, err)
return
}
if errors.Is(err, state.ErrServerURLMismatch) {
log.Error("Deployment initialization failure - Server URL Mismatch", "error", err.Error())
// Redeployments must go to the same server
w.WriteHeader(http.StatusConflict)
w.Write([]byte(err.Error()))
Expand All @@ -73,18 +75,35 @@ func PostDeploymentHandlerFunc(
if aerr, ok := types.IsAgentError(err); ok {
if aerr.Code == types.ErrorUnknownTOMLKey {
apiErr := types.APIErrorUnknownTOMLKeyFromAgentError(*aerr)
log.Error("Deployment initialization failure", "apiErr", apiErr.Error())
apiErr.JSONResponse(w)
return
}
if aerr.Code == types.ErrorInvalidTOML {
apiErr := types.APIErrorInvalidTOMLFileFromAgentError(*aerr)
log.Error("Deployment initialization failure", "apiErr", apiErr.Error())
apiErr.JSONResponse(w)
return
}
if aerr.Code == types.ErrorTomlValidationError {
configPath := config.GetConfigPath(projectDir, b.ConfigName)
apiErr := types.APIErrorTomlValidationFromAgentError(*aerr, configPath.String())
log.Error("Deployment initialization failure", "apiErr", apiErr.Error())
apiErr.JSONResponse(w)
return
}
if aerr.Code == types.ErrorTomlUnknownError {
configPath := config.GetConfigPath(projectDir, b.ConfigName)
apiErr := types.APIErrorTomlUnknownErrorFromAgentError(*aerr, configPath.String())
log.Error("Deployment initialization failure", "apiErr", apiErr.Error())
apiErr.JSONResponse(w)
return
}
}
BadRequest(w, req, log, err)
return
}
log.Debug("New account derived state created", "account", b.AccountName, "config", b.ConfigName)

response := PostDeploymentsReponse{
LocalID: localID,
Expand Down
Loading

0 comments on commit 4eee8ec

Please sign in to comment.