Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Follow-on for dismissed deployment support PR #2524

2 changes: 1 addition & 1 deletion extensions/vscode/src/api/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1254,7 +1254,7 @@ export interface PublishFailure extends EventStreamMessage {
data: {
dashboardUrl: string;
url: string;
canceled?: string; // not defined if not user cancelled. Value of "true" if true.
canceled?: string; // not defined if not user canceled. Value of "true" if true.
// and other non-defined attributes
};
error: string; // translated internally
Expand Down
25 changes: 17 additions & 8 deletions extensions/vscode/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export function displayEventStreamMessage(msg: EventStreamMessage): string {
if (msg.data.dashboardUrl) {
return `Deployment failed, click to view Connect logs: ${msg.data.dashboardUrl}`;
}
if (msg.data.canceled === "true") {
return "Deployment canceled";
}
return "Deployment failed";
}
if (msg.error !== undefined) {
Expand All @@ -95,8 +98,8 @@ export class EventStream extends Readable implements Disposable {
private messages: EventStreamMessage[] = [];
// Map to store event callbacks
private callbacks: Map<string, EventStreamRegistration[]> = new Map();
// Cancelled Event Streams - Suppressed when received
private cancelledLocalIDs: string[] = [];
// Canceled Event Streams - Suppressed when received
private canceledLocalIDs: string[] = [];

/**
* Creates a new instance of the EventStream class.
Expand Down Expand Up @@ -170,19 +173,25 @@ export class EventStream extends Readable implements Disposable {
* @returns undefined
*/
public suppressMessages(localId: string) {
this.cancelledLocalIDs.push(localId);
this.canceledLocalIDs.push(localId);
}

private processMessage(msg: EventStreamMessage) {
const localId = msg.data.localId;
if (localId && this.cancelledLocalIDs.includes(localId)) {
// Some log messages passed on from Connect include
// the localId using snake_case, rather than pascalCase.
// To filter correctly, we need to check for both.

const localId = msg.data.localId || msg.data.local_id;
if (localId && this.canceledLocalIDs.includes(localId)) {
// suppress and ignore
return;
}

// Trace message
// console.debug(
// `eventSource trace: ${event.type}: ${JSON.stringify(event)}`,
// );
// Uncomment the following code if you want to dump every message to the
// console as it is received.
// console.debug(`eventSource trace: ${msg.type}: ${JSON.stringify(msg)}`);

// Add the message to the messages array
this.messages.push(msg);
// Emit a 'message' event with the message as the payload
Expand Down
2 changes: 1 addition & 1 deletion extensions/vscode/src/multiStepInputs/newCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function newCredential(
totalSteps: -1,
data: {
// each attribute is initialized to undefined
// to be returned when it has not been cancelled
// to be returned when it has not been canceled
url: startingServerUrl, // eventual type is string
apiKey: <string | undefined>undefined, // eventual type is string
name: <string | undefined>undefined, // eventual type is string
Expand Down
2 changes: 1 addition & 1 deletion extensions/vscode/src/multiStepInputs/newDeployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ export async function newDeployment(
title: "Select Entrypoint File (main file for your project)",
});
if (!fileUris || !fileUris[0]) {
// cancelled.
// canceled.
continue;
}
const fileUri = fileUris[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export async function selectNewOrExistingConfig(
totalSteps: -1,
data: {
// each attribute is initialized to undefined
// to be returned when it has not been cancelled to assist type guards
// to be returned when it has not been canceled to assist type guards
// Note: We can't initialize existingConfigurationName to a specific initial
// config, as we then wouldn't be able to detect if the user hit ESC to exit
// the selection. :-(
Expand Down
20 changes: 20 additions & 0 deletions extensions/vscode/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,26 @@ export class PublisherState implements Disposable {
}
}

updateContentRecord(
newValue: ContentRecord | PreContentRecord | PreContentRecordWithConfig,
) {
const existingContentRecord = this.findContentRecord(
newValue.saveName,
newValue.projectDir,
);
if (existingContentRecord) {
const crIndex = this.contentRecords.findIndex(
(contentRecord) =>
contentRecord.deploymentPath === existingContentRecord.deploymentPath,
);
if (crIndex !== -1) {
this.contentRecords[crIndex] = newValue;
} else {
this.contentRecords.push(newValue);
}
}
}

async getSelectedConfiguration() {
const contentRecord = await this.getSelectedContentRecord();
if (!contentRecord) {
Expand Down
26 changes: 20 additions & 6 deletions extensions/vscode/src/views/deployProgress.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
// Copyright (C) 2024 by Posit Software, PBC.

import { ProgressLocation, Uri, env, window } from "vscode";
import { EventStreamMessage, eventMsgToString, useApi } from "src/api";
import {
EventStreamMessage,
eventMsgToString,
useApi,
ContentRecord,
PreContentRecord,
PreContentRecordWithConfig,
} from "src/api";
import { EventStream, UnregisterCallback } from "src/events";
import { getSummaryStringFromError } from "src/utils/errors";

type UpdateActiveContentRecordCB = (
contentRecord: ContentRecord | PreContentRecord | PreContentRecordWithConfig,
) => void;

export function deployProject(
deploymentName: string,
dir: string,
localID: string,
stream: EventStream,
updateActiveContentRecordCB: UpdateActiveContentRecordCB,
) {
window.withProgress(
{
Expand Down Expand Up @@ -38,11 +50,15 @@ export function deployProject(
streamID = "NEVER_A_VALID_STREAM";
unregisterAll();
try {
await api.contentRecords.cancelDeployment(
const response = await api.contentRecords.cancelDeployment(
deploymentName,
dir,
localID,
);

// update the UX locally
updateActiveContentRecordCB(response.data);

// we must have been successful...
// inject a psuedo end of publishing event
stream.injectMessage({
Expand All @@ -53,7 +69,7 @@ export function deployProject(
url: "",
// and other non-defined attributes
localId: localID,
cancelled: "true",
canceled: "true",
message:
"Deployment has been dismissed (but will continue to be processed on the Connect Server).",
},
Expand All @@ -65,9 +81,7 @@ export function deployProject(
"deployProject, token.onCancellationRequested",
error,
);
window.showErrorMessage(
`Unable to abort deployment: ${summary}`,
);
window.showErrorMessage(`Unable to abort deployment: ${summary}`);
}
resolveCB();
});
Expand Down
17 changes: 16 additions & 1 deletion extensions/vscode/src/views/homeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
useApi,
AllContentRecordTypes,
EnvironmentConfig,
PreContentRecordWithConfig,
} from "src/api";
import { EventStream } from "src/events";
import { getPythonInterpreterPath, getRInterpreterPath } from "../utils/vscode";
Expand Down Expand Up @@ -213,6 +214,7 @@ export class HomeViewProvider implements WebviewViewProvider, Disposable {
projectDir,
response.data.localId,
this.stream,
this.updateActiveContentRecordLocally.bind(this),
);
} catch (error: unknown) {
// Most failures will occur on the event stream. These are the ones which
Expand Down Expand Up @@ -315,6 +317,19 @@ export class HomeViewProvider implements WebviewViewProvider, Disposable {
}
}

private updateActiveContentRecordLocally(
activeContentRecord:
| ContentRecord
| PreContentRecord
| PreContentRecordWithConfig,
) {
// update our local state, so we don't wait on file refreshes
this.state.updateContentRecord(activeContentRecord);

// refresh the webview
this.updateWebViewViewContentRecords();
}

private onPublishStart() {
this.webviewConduit.sendMsg({
kind: HostToWebviewMessageType.PUBLISH_START,
Expand Down Expand Up @@ -954,7 +969,7 @@ export class HomeViewProvider implements WebviewViewProvider, Disposable {
activeConfig.configuration.environment,
);
if (name === undefined) {
// Cancelled by the user
// Canceled by the user
return;
}

Expand Down
23 changes: 18 additions & 5 deletions extensions/vscode/src/views/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ enum LogStageStatus {
inProgress,
completed,
failed,
canceled,
}

type LogStage = {
Expand Down Expand Up @@ -182,14 +183,17 @@ export class LogsTreeDataProvider implements TreeDataProvider<LogsTreeItem> {
});

this.stream.register("publish/failure", async (msg: EventStreamMessage) => {
this.publishingStage.status = LogStageStatus.failed;
const failedOrCanceledStatus = msg.data.canceled
? LogStageStatus.canceled
: LogStageStatus.failed;
this.publishingStage.status = failedOrCanceledStatus;
this.publishingStage.events.push(msg);

this.stages.forEach((stage) => {
if (stage.status === LogStageStatus.notStarted) {
stage.status = LogStageStatus.neverStarted;
} else if (stage.status === LogStageStatus.inProgress) {
stage.status = LogStageStatus.failed;
stage.status = failedOrCanceledStatus;
}
});

Expand All @@ -204,8 +208,8 @@ export class LogsTreeDataProvider implements TreeDataProvider<LogsTreeItem> {
errorMessage = handleEventCodedError(msg);
} else {
errorMessage =
msg.data.cancelled === "true"
? `Deployment cancelled: ${msg.data.message}`
msg.data.canceled === "true"
? `Deployment canceled: ${msg.data.message}`
: `Deployment failed: ${msg.data.message}`;
}
const selection = await window.showErrorMessage(errorMessage, ...options);
Expand Down Expand Up @@ -259,7 +263,11 @@ export class LogsTreeDataProvider implements TreeDataProvider<LogsTreeItem> {
(msg: EventStreamMessage) => {
const stage = this.stages.get(stageName);
if (stage) {
stage.status = LogStageStatus.failed;
if (msg.data.canceled === "true") {
stage.status = LogStageStatus.canceled;
} else {
stage.status = LogStageStatus.failed;
}
stage.events.push(msg);
}
this.refresh();
Expand Down Expand Up @@ -413,6 +421,11 @@ export class LogsTreeStageItem extends TreeItem {
this.iconPath = new ThemeIcon("error");
this.collapsibleState = TreeItemCollapsibleState.Expanded;
break;
case LogStageStatus.canceled:
this.label = this.stage.inactiveLabel;
this.iconPath = new ThemeIcon("circle-slash");
this.collapsibleState = TreeItemCollapsibleState.Expanded;
break;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ const lastStatusDescription = computed(() => {
: "Not Yet Deployed";
}
if (isAbortedContentRecord.value) {
return "Last Deployment Cancelled";
return "Last Deployment Canceled";
}
return "Last Deployment Successful";
});
Expand Down
4 changes: 2 additions & 2 deletions extensions/vscode/webviews/homeView/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export default defineConfig({
enabled: true,
thresholds: {
functions: 30.13,
lines: 17.46,
lines: 17.37,
branches: 44.82,
statements: 17.46,
statements: 17.37,
autoUpdate: true,
},
},
Expand Down
2 changes: 1 addition & 1 deletion internal/deployment/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func (d *Deployment) WriteFile(
return existingDeployment, nil
}
if existingDeployment.AbortedAt != "" {
log.Debug("Skipping deployment record update since deployment has been cancelled")
log.Debug("Skipping deployment record update since deployment has been canceled")
return existingDeployment, nil
}
}
Expand Down
6 changes: 3 additions & 3 deletions internal/publish/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,10 @@ func (p *defaultPublisher) writeDeploymentRecord(forceUpdate bool) (*deployment.

func CancelDeployment(
deploymentPath util.AbsolutePath,
localID state.LocalDeploymentID,
localID string,
dotNomad marked this conversation as resolved.
Show resolved Hide resolved
log logging.Logger,
) (*deployment.Deployment, error) {
// This function only marks the deployment record as being cancelled.
// This function only marks the deployment record as being canceled.
// It does not cancel the anonymous function which is publishing to the server
// This is because the server API does not support cancellation at this time.

Expand All @@ -240,7 +240,7 @@ func CancelDeployment(
target.AbortedAt = time.Now().Format(time.RFC3339)

// Possibly update the deployment file
d, err := target.WriteFile(deploymentPath, target.LocalID, false, log)
d, err := target.WriteFile(deploymentPath, localID, false, log)
return d, err
}

Expand Down
2 changes: 1 addition & 1 deletion internal/services/api/api_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func RouterHandlerFunc(base util.AbsolutePath, lister accounts.AccountList, log
Methods(http.MethodPost)

// POST /api/deployments/$NAME/cancel/$LOCALID cancels a deployment
r.Handle(ToPath("deployments", "{name}", "cancel", "{localid}"), PostCancelDeploymentHandlerFunc(base, log)).
r.Handle(ToPath("deployments", "{name}", "cancel", "{localid}"), PostDeploymentCancelHandlerFunc(base, log)).
Methods(http.MethodPost)

// DELETE /api/deployments/$NAME
Expand Down
4 changes: 4 additions & 0 deletions internal/services/api/deployment_dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type preDeploymentDTO struct {
ServerURL string `json:"serverUrl"`
SaveName string `json:"saveName"`
CreatedAt string `json:"createdAt"`
LocalID string `toml:"local_id,omitempty" json:"localId"`
AbortedAt string `toml:"aborted_at,omitempty" json:"abortedAt"`
ConfigName string `json:"configurationName,omitempty"`
ConfigPath string `json:"configurationPath,omitempty"`
Error *types.AgentError `json:"deploymentError,omitempty"`
Expand Down Expand Up @@ -105,6 +107,8 @@ func deploymentAsDTO(d *deployment.Deployment, err error, projectDir util.Absolu
ServerURL: d.ServerURL,
SaveName: saveName, // TODO: remove this duplicate (remove frontend references first)
CreatedAt: d.CreatedAt,
AbortedAt: d.AbortedAt,
LocalID: d.LocalID,
ConfigName: d.ConfigName,
ConfigPath: configPath,
Error: d.Error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import (
"github.com/posit-dev/publisher/internal/deployment"
"github.com/posit-dev/publisher/internal/logging"
"github.com/posit-dev/publisher/internal/publish"
"github.com/posit-dev/publisher/internal/state"
"github.com/posit-dev/publisher/internal/util"
)

func PostCancelDeploymentHandlerFunc(base util.AbsolutePath, log logging.Logger) http.HandlerFunc {
func PostDeploymentCancelHandlerFunc(base util.AbsolutePath, log logging.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
name := mux.Vars(req)["name"]
localId := mux.Vars(req)["localid"]
Expand All @@ -26,7 +25,7 @@ func PostCancelDeploymentHandlerFunc(base util.AbsolutePath, log logging.Logger)
return
}
path := deployment.GetDeploymentPath(projectDir, name)
latest, err := publish.CancelDeployment(path, state.LocalDeploymentID(localId), log)
latest, err := publish.CancelDeployment(path, localId, log)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
http.NotFound(w, req)
Expand Down
Loading
Loading