Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into sagerb-catch-agent-er…
Browse files Browse the repository at this point in the history
…ror-deploying-config-with-toml-error
  • Loading branch information
sagerb committed Oct 11, 2024
2 parents 5f770a1 + 5ca5125 commit 61a3621
Show file tree
Hide file tree
Showing 21 changed files with 748 additions and 51 deletions.
96 changes: 96 additions & 0 deletions extensions/vscode/src/api/types/error.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (C) 2024 by Posit Software, PBC.

import { describe, expect, test } from "vitest";
import {
isAgentErrorTypeUnknown,
isAgentErrorInvalidTOML,
isAgentErrorDeployedContentNotRunning,
} from "./error";

describe("Agent Errors", () => {
test("isAgentErrorTypeUnknown", () => {
let result = isAgentErrorTypeUnknown({
code: "unknown",
msg: "",
operation: "",
data: {},
});
expect(result).toBe(true);

result = isAgentErrorTypeUnknown({
code: "resourceNotFound",
msg: "",
operation: "",
data: {},
});
expect(result).toBe(true);

result = isAgentErrorTypeUnknown({
code: "unknownTOMLKey",
msg: "",
operation: "",
data: {},
});
expect(result).toBe(false);

result = isAgentErrorTypeUnknown({
code: "invalidTOML",
msg: "",
operation: "",
data: {},
});
expect(result).toBe(false);

result = isAgentErrorTypeUnknown({
code: "deployedContentNotRunning",
msg: "",
operation: "",
data: {},
});
expect(result).toBe(false);
});

test("isAgentErrorInvalidTOML", () => {
let result = isAgentErrorInvalidTOML({
code: "unknown",
msg: "",
operation: "",
data: {},
});
expect(result).toBe(false);

result = isAgentErrorInvalidTOML({
code: "unknownTOMLKey",
msg: "",
operation: "",
data: {},
});
expect(result).toBe(true);

result = isAgentErrorInvalidTOML({
code: "invalidTOML",
msg: "",
operation: "",
data: {},
});
expect(result).toBe(true);
});

test("isAgentErrorDeployedContentNotRunning", () => {
let result = isAgentErrorDeployedContentNotRunning({
code: "unknown",
msg: "",
operation: "",
data: {},
});
expect(result).toBe(false);

result = isAgentErrorDeployedContentNotRunning({
code: "deployedContentNotRunning",
msg: "",
operation: "",
data: {},
});
expect(result).toBe(true);
});
});
21 changes: 18 additions & 3 deletions extensions/vscode/src/api/types/error.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
// Copyright (C) 2023 by Posit Software, PBC.

import { ErrorCode } from "../../utils/errorTypes";

type AgentErrorBase = {
code: string;
code: ErrorCode;
msg: string;
operation: string;
};

export type AgentError = AgentErrorTypeUnknown | AgentErrorInvalidTOML;
export type AgentError =
| AgentErrorTypeUnknown
| AgentErrorInvalidTOML
| AgentErrorContentNotRunning;

export type AgentErrorTypeUnknown = AgentErrorBase & {
data: {
Expand All @@ -19,7 +24,9 @@ export type AgentErrorTypeUnknown = AgentErrorBase & {
export const isAgentErrorTypeUnknown = (
e: AgentError,
): e is AgentErrorTypeUnknown => {
return !isAgentErrorInvalidTOML(e);
return (
!isAgentErrorInvalidTOML(e) && !isAgentErrorDeployedContentNotRunning(e)
);
};

export type AgentErrorInvalidTOML = AgentErrorBase & {
Expand All @@ -36,3 +43,11 @@ export const isAgentErrorInvalidTOML = (
): e is AgentErrorInvalidTOML => {
return e.code === "invalidTOML" || e.code === "unknownTOMLKey";
};

export type AgentErrorContentNotRunning = AgentErrorBase;

export const isAgentErrorDeployedContentNotRunning = (
e: AgentError,
): e is AgentErrorContentNotRunning => {
return e.code === "deployedContentNotRunning";
};
36 changes: 36 additions & 0 deletions extensions/vscode/src/eventErrors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
isCodedEventErrorMessage,
isEvtErrDeploymentFailed,
isEvtErrRenvLockPackagesReadingFailed,
isEvtErrRequirementsReadingFailed,
isEvtErrDeployedContentNotRunning,
handleEventCodedError,
} from "./eventErrors";
import { ErrorCode } from "./utils/errorTypes";
Expand Down Expand Up @@ -66,6 +68,30 @@ describe("Event errors", () => {
expect(result).toBe(true);
});

test("isEvtErrRequirementsReadingFailed", () => {
// Message with another error code
let streamMsg = mkEventStreamMsg({}, "unknown");
let result = isEvtErrRequirementsReadingFailed(streamMsg);
expect(result).toBe(false);

// Message with error code
streamMsg = mkEventStreamMsg({}, "requirementsFileReadingError");
result = isEvtErrRequirementsReadingFailed(streamMsg);
expect(result).toBe(true);
});

test("isEvtErrDeployedContentNotRunning", () => {
// Message with another error code
let streamMsg = mkEventStreamMsg({}, "unknown");
let result = isEvtErrDeployedContentNotRunning(streamMsg);
expect(result).toBe(false);

// Message with error code
streamMsg = mkEventStreamMsg({}, "deployedContentNotRunning");
result = isEvtErrDeployedContentNotRunning(streamMsg);
expect(result).toBe(true);
});

test("handleEventCodedError", () => {
const msgData = {
dashboardUrl: "https://here.it.is/content/abcdefg",
Expand All @@ -86,5 +112,15 @@ describe("Event errors", () => {
streamMsg = mkEventStreamMsg(msgData, "renvlockPackagesReadingError");
resultMsg = handleEventCodedError(streamMsg);
expect(resultMsg).toBe("Could not scan R packages from renv lockfile");

msgData.message = "requirements.txt is missing";
streamMsg = mkEventStreamMsg(msgData, "requirementsFileReadingError");
resultMsg = handleEventCodedError(streamMsg);
expect(resultMsg).toBe("requirements.txt is missing");

msgData.message = "deployed content not running";
streamMsg = mkEventStreamMsg(msgData, "deployedContentNotRunning");
resultMsg = handleEventCodedError(streamMsg);
expect(resultMsg).toBe("deployed content not running");
});
});
36 changes: 29 additions & 7 deletions extensions/vscode/src/eventErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ type lockfileReadingEvtErr = baseEvtErr & {
lockfile: string;
};

type requirementsReadingEvtErr = baseEvtErr & {
requirementsFile: string;
};

export const isEvtErrDeploymentFailed = (
emsg: EventStreamMessageErrorCoded,
): emsg is EventStreamMessageErrorCoded<baseEvtErr> => {
Expand All @@ -37,17 +41,35 @@ export const isEvtErrRenvLockPackagesReadingFailed = (
return emsg.errCode === "renvlockPackagesReadingError";
};

export const isEvtErrRequirementsReadingFailed = (
emsg: EventStreamMessageErrorCoded,
): emsg is EventStreamMessageErrorCoded<requirementsReadingEvtErr> => {
return emsg.errCode === "requirementsFileReadingError";
};

export const isEvtErrDeployedContentNotRunning = (
emsg: EventStreamMessageErrorCoded,
): emsg is EventStreamMessageErrorCoded<baseEvtErr> => {
return emsg.errCode === "deployedContentNotRunning";
};

export const useEvtErrKnownMessage = (
emsg: EventStreamMessageErrorCoded,
): boolean => {
return (
isEvtErrDeploymentFailed(emsg) ||
isEvtErrRenvLockPackagesReadingFailed(emsg) ||
isEvtErrRequirementsReadingFailed(emsg) ||
isEvtErrDeployedContentNotRunning(emsg)
);
};

export const handleEventCodedError = (
emsg: EventStreamMessageErrorCoded,
): string => {
if (isEvtErrDeploymentFailed(emsg)) {
return emsg.data.message;
}

if (isEvtErrRenvLockPackagesReadingFailed(emsg)) {
if (useEvtErrKnownMessage(emsg)) {
return emsg.data.message;
}

const unknownErrMsg = emsg.data.error || emsg.data.message;
return `${unknownErrMsg}`;
return emsg.data.error || emsg.data.message;
};
4 changes: 3 additions & 1 deletion extensions/vscode/src/utils/errorTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export type ErrorCode =
| "invalidConfigFile"
| "errorCertificateVerification"
| "deployFailed"
| "renvlockPackagesReadingError";
| "renvlockPackagesReadingError"
| "requirementsFileReadingError"
| "deployedContentNotRunning";

export type axiosErrorWithJson<T = { code: ErrorCode; details: unknown }> =
AxiosError & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@
v-if="home.selectedContentRecord.deploymentError"
class="last-deployment-details last-deployment-error"
>
<span class="codicon codicon-error error-icon"></span>
<span class="codicon codicon-alert"></span>
<TextStringWithAnchor
:message="home.selectedContentRecord?.deploymentError?.msg"
:splitOptions="ErrorMessageSplitOptions"
Expand All @@ -191,7 +191,7 @@
@click="viewContent"
class="w-full"
>
View Content
{{ deployedContentButtonLabel }}
</vscode-button>
</div>
</div>
Expand Down Expand Up @@ -235,6 +235,7 @@ import TextStringWithAnchor from "./TextStringWithAnchor.vue";
import {
AgentError,
isAgentErrorInvalidTOML,
isAgentErrorDeployedContentNotRunning,
} from "../../../../src/api/types/error";

const home = useHomeStore();
Expand Down Expand Up @@ -438,6 +439,20 @@ const getActiveConfigTOMLErrorDetails = computed(() => {
return "";
});

const isDeployedContentOnError = computed((): boolean => {
const deploymentError = home.selectedContentRecord?.deploymentError;
return Boolean(
deploymentError && isAgentErrorDeployedContentNotRunning(deploymentError),
);
});

const deployedContentButtonLabel = computed((): string => {
if (isDeployedContentOnError.value) {
return "View Deployment Logs";
}
return "View Content";
});

const onEditConfigurationWithTOMLError = () => {
const agentError = getActiveConfigError.value;
if (agentError && isAgentErrorInvalidTOML(agentError)) {
Expand Down Expand Up @@ -492,8 +507,14 @@ const onAssociateDeployment = () => {
};

const viewContent = () => {
if (home.selectedContentRecord?.dashboardUrl) {
navigateToUrl(home.selectedContentRecord.dashboardUrl);
const record = home.selectedContentRecord;
if (!record) {
return;
}
if (isDeployedContentOnError.value && !isPreContentRecord(record)) {
navigateToUrl(record.logsUrl);
} else if (record.dashboardUrl) {
navigateToUrl(record.dashboardUrl);
}
};
</script>
Expand Down
26 changes: 26 additions & 0 deletions internal/clients/connect/apptypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,29 @@ func AppModeFromType(t config.ContentType) AppMode {
}
return mode
}

var contentTypeConnectMap = map[AppMode]config.ContentType{
StaticMode: config.ContentTypeHTML,
StaticJupyterMode: config.ContentTypeJupyterNotebook,
JupyterVoilaMode: config.ContentTypeJupyterVoila,
PythonBokehMode: config.ContentTypePythonBokeh,
PythonDashMode: config.ContentTypePythonDash,
PythonFastAPIMode: config.ContentTypePythonFastAPI,
PythonAPIMode: config.ContentTypePythonFlask,
PythonShinyMode: config.ContentTypePythonShiny,
PythonStreamlitMode: config.ContentTypePythonStreamlit,
ShinyQuartoMode: config.ContentTypeQuartoShiny,
StaticQuartoMode: config.ContentTypeQuarto,
PlumberAPIMode: config.ContentTypeRPlumber,
ShinyMode: config.ContentTypeRShiny,
ShinyRmdMode: config.ContentTypeRMarkdownShiny,
StaticRmdMode: config.ContentTypeRMarkdown,
}

func ContentTypeFromAppMode(a AppMode) config.ContentType {
contentType, ok := contentTypeConnectMap[a]
if !ok {
contentType = config.ContentTypeUnknown
}
return contentType
}
Loading

0 comments on commit 61a3621

Please sign in to comment.