Skip to content

Commit

Permalink
Merge pull request #2524 from posit-dev/sagerb-support-dismissed-depl…
Browse files Browse the repository at this point in the history
…oyment-follow-on

Follow-on for dismissed deployment support PR
  • Loading branch information
sagerb authored Jan 15, 2025
2 parents 158ded1 + 5e2b8a9 commit 0376c4d
Show file tree
Hide file tree
Showing 17 changed files with 233 additions and 35 deletions.
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 @@ -406,7 +406,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,
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

0 comments on commit 0376c4d

Please sign in to comment.