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

Deleting current workspace switches to another instead of closing [ backend implementation ] #1623

Merged
merged 17 commits into from
Dec 29, 2024

Conversation

Jalileh
Copy link
Contributor

@Jalileh Jalileh commented Dec 25, 2024

I did not mean to close the previous pr, anyway i tried to implement what you suggested, the backend now does most of it
and DeleteWorkspace will return an unclaimed id and avoid closing the window.

        const moveToNewWorkspace = await WorkspaceService.DeleteWorkspace(workspaceId) 
        console.log("delete-workspace done", workspaceId, ww?.waveWindowId);
        if (ww?.workspaceId == workspaceId){
            if ( workspaceList?.length > 1 ) {
                   await ww.switchWorkspace(moveToNewWorkspace)
            } else {
                    console.log("delete-workspace closing window", workspaceId, ww?.waveWindowId);
                    ww.destroy();
            }
        }
    });

unknown_2024 12 26-17 05
unknown_2024 12 26-17 06

Copy link
Contributor

coderabbitai bot commented Dec 25, 2024

Walkthrough

The pull request introduces comprehensive changes across multiple files to enhance workspace and tab management functionality. Key modifications include updates to the DeleteWorkspace method's signature and return type across various components, transitioning from returning Promise<void> to Promise<string> in the frontend services and from returning two values to three in the backend services.

The new return value, claimableWorkspace, provides additional context when deleting a workspace and is integrated into the logic of the workspace service and core functions. In the Electron main window (emain-window.ts), a new type WindowOpts has been defined, and new methods queueCreateTab and queueCloseTab have been added to manage tab operations. Additionally, the workspace deletion logic has been refined to handle cases where the current workspace is deleted, ensuring a smooth transition between workspaces.

These changes enhance the functionality related to workspace management and tab handling, ensuring better state management.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59b8d28 and c8139f9.

📒 Files selected for processing (1)
  • emain/emain-window.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • emain/emain-window.ts

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Jalileh Jalileh changed the title Deleting current workspace switches to another instead of closing [ backend implementation Deleting current workspace switches to another instead of closing [ backend implementation ] Dec 25, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
pkg/service/workspaceservice/workspaceservice.go (1)

100-103: No workspace deletion performed but no error: Clarify logic.

When deleted == false here, the function returns no error and no updates. Yet, the user may expect more insight into why the workspace was not deleted (e.g., named, pinned tabs). Consider adding logs or comments for clarity.

pkg/wcore/workspace.go (1)

166-166: Potentially unify error handling for window closure.

Here, if CloseWindow fails, you return a wrapped error. In other cases (like lines 162–163), you skip the closure and return early. Consider consolidating this logic to make the control flow more transparent and reduce branching.

emain/emain-window.ts (2)

306-308: Handle potential undefined curWorkspace.

The comment indicates a potential bug if curWorkspace is undefined after a workspace deletion. Consider robust error-handling or early-return logic in case curWorkspace is null to avoid runtime errors.

 if (!workspaceList?.find((wse) => wse.workspaceid === workspaceId)?.windowid) {
   const curWorkspace = await WorkspaceService.GetWorkspace(this.workspaceId);
-  if (curWorkspace && isNonEmptyUnsavedWorkspace(curWorkspace)) {
+  if (!curWorkspace) {
+    console.warn(`Workspace ${this.workspaceId} not found or was already deleted.`);
+    return;
+  }
+
+  if (isNonEmptyUnsavedWorkspace(curWorkspace)) {
     console.log(
       `existing unsaved workspace ${this.workspaceId} has content, opening workspace ${workspaceId} in new window`
     );
     await createWindowForWorkspace(workspaceId);
     return;
   }
 }

704-713: Check for null or empty moveToNewWorkspace and handle errors.

When deleting a workspace, DeleteWorkspace returns moveToNewWorkspace. Verify that a valid workspace ID is returned before calling switchWorkspace, or handle the null/empty string scenario. Consider wrapping the call in a try/catch block to ensure errors in the backend do not leave the UI in an inconsistent state.

 const moveToNewWorkspace = await WorkspaceService.DeleteWorkspace(workspaceId);
 console.log("delete-workspace done", workspaceId, ww?.waveWindowId);
 if (ww?.workspaceId == workspaceId) {
   if (workspaceList?.length > 1) {
-    await ww.switchWorkspace(moveToNewWorkspace);
+    if (moveToNewWorkspace) {
+      await ww.switchWorkspace(moveToNewWorkspace);
+    } else {
+      console.warn(`No valid workspace to move to after deletion of ${workspaceId}. Closing window instead.`);
+      ww.destroy();
+    }
   } else {
     console.log("delete-workspace closing window", workspaceId, ww?.waveWindowId);
     ww.destroy();
   }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1cf539 and 2d3dca6.

📒 Files selected for processing (6)
  • .vscode/launch.json (1 hunks)
  • emain/emain-window.ts (4 hunks)
  • frontend/app/store/services.ts (1 hunks)
  • pkg/service/workspaceservice/workspaceservice.go (2 hunks)
  • pkg/wcore/window.go (2 hunks)
  • pkg/wcore/workspace.go (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .vscode/launch.json
🧰 Additional context used
🪛 golangci-lint (1.62.2)
pkg/wcore/workspace.go

137-137: ineffectual assignment to err

(ineffassign)

🔇 Additional comments (14)
pkg/wcore/window.go (2)

55-55: Consider handling or documenting the ignored return value.

Here, the second return parameter from DeleteWorkspace (i.e., claimableWorkspace) is ignored. If you intend to switch a user to this "claimable" workspace rather than deleting it outright, consider returning or logging this value to provide better clarity in the workflow. Otherwise, document why it’s safe to ignore.


134-134: Re-examine the ignored return value for future workspace logic.

As above, the second return parameter (claimableWorkspace) is also ignored here. Double-check whether ignoring it is consistent with the intended behavior. If you plan to reuse or transition to any unclaimed workspace, you may need to capture or handle that value rather than discarding it.

frontend/app/store/services.ts (1)

192-192: Confirm the front-end usage of the new return type.

The DeleteWorkspace method now returns a Promise<string>. Ensure that all invocations of this method correctly handle or ignore the returned string and that no callers still expect a void return type.

pkg/service/workspaceservice/workspaceservice.go (4)

57-58: Stylistic update with trailing comma is acceptable.

Adding the trailing comma to the event publishing struct is consistent with Go's recommended formatting style. No further changes are required here.


91-91: Signature change to include a string return is consistent with the PR objective.

The updated signature returning (waveobj.UpdatesRtnType, string, error) aligns well with the new requirement to track unclaimed workspaces. Ensure all callers are updated to handle the additional string.


95-98: Interpretation of an unclaimed workspace short-circuit.

Returning immediately when claimableWorkspace != "" (line 96) means your updates or errors are skipped past. Confirm that this early return is the correct behavior and that ignoring waveobj.UpdatesRtnType is desired in this scenario.


110-110: Asynchronously sending update events is a good approach.

The use of a goroutine for sending update events prevents blocking. Ensure all potential race conditions and concurrency issues are handled, particularly if the rest of the code modifies internal state after spawning the goroutine.

pkg/wcore/workspace.go (5)

69-70: Minor consistency fix with trailing comma.

Including the trailing comma when creating the WaveEvent struct is stylistically aligned. This is a minor improvement and is good to keep consistent across the codebase.


118-118: Signature now returns a “claimableWorkspace” string.

This change reflects the new feature requirements. Verify that all existing calls to DeleteWorkspace are updated accordingly to capture or consciously ignore this new string value.


122-126: Graceful handling of named workspaces.

The early return skips deletion when a workspace is named and has tabs. Verify that this logic aligns with the updated product requirements, especially if the user truly wants the workspace removed under certain conditions (e.g., forced deletion).


148-164: Check logic when a workspace is unclaimed.

Upon discovering UnclaimedWorkspace != "", you return immediately at line 162, skipping a window closure. Confirm that the calling code can handle this scenario where the old workspace is deleted and the user is expected to switch to the unclaimed workspace.


169-169: Ensuring final return is correct.

When no unclaimed workspace remains and everything else succeeds, returning (true, "", nil) is appropriate. Validate that callers correctly interpret an empty claimable workspace string as “no alternative workspace available.”

emain/emain-window.ts (2)

21-22: Consider removing unused imports or verify usage.

Importing { time } from "console" and { timestamp } from "rxjs" can introduce confusion if they are not actually used in the code. Please ensure they are necessary, or remove them if not used to keep the code clean.


422-422: Add unit tests to verify queued tab creations.

The queueCreateTab function adds a new tab creation request to _queueActionInternal. Ensuring consistent handling of pinned vs non-pinned tabs and concurrency would be beneficial. Please confirm that you have sufficient test coverage for these scenarios.

Copy link
Member

@esimkowitz esimkowitz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Left some suggestions and questions

return false, "", fmt.Errorf("error retrieving workspaceList: %w", err)
}
UnclaimedWorkspace := ""
for _, ws := range workspaces {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I liked your old logic which would attempt to find the workspace at index closest to the one that was deleted, could you incorporate that here?

I think you can do this in one pass. Create three variables, curWsIndex, freeWsIndexBeforeCurrent and freeWsIndexAfterCurrent. Then, you iterate through the workspace list, setting freeWsIndexBeforeCurrent to the index in the workspace list where you find a free workspace (not claimed by a window). Once you find the current index, you set curWsIndex and from there you continue iterating until you find another free workspace, where you set freeWsIndexAfterCurrent. Then, you just take whichever one of the two indices is closer to curWsIndex (or whichever isn't null).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure i can implement that if we can ignore this return error, because im relying on getting any claimable workspace to not upset this code because deleteworkspace also runs in switchworkspace for some reason i dont know
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah it runs in switch workspace to clean up unsaved workspaces. For saved ones, delete without a force flag is a no-op

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do I actually retrieve the error string in the error data structure? if I can get the string to reliably know if 'not found' happened I wont have to limit the claimworkspace implementation, I only saw msg inside of it but couldn't access it, sorry I'm new to go.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can return empty string if nothing is deleted. This should never happen if you call deleteworkspace from the workspace service since that passes the force flag as true to the wcore implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK ill implement the old logic tomorrow and notify you, ty!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pkg/wcore/workspace.go (1)

148-158: Enhance workspace selection logic.

The current implementation selects the first unclaimed workspace. Consider implementing the suggested approach of selecting the closest workspace to provide a more intuitive user experience.

Consider this approach:

-		UnclaimedWorkspace := ""
-		for _, ws := range workspaces {
-			if ws.WindowId == "" && workspaceId != ws.WorkspaceId {
-				UnclaimedWorkspace = ws.WorkspaceId
-				break
-			}
-		}
+		var (
+			curWsIndex int
+			freeWsIndexBeforeCurrent = -1
+			freeWsIndexAfterCurrent = -1
+		)
+		
+		// Find the current workspace index and free workspaces
+		for i, ws := range workspaces {
+			if ws.WorkspaceId == workspaceId {
+				curWsIndex = i
+				continue
+			}
+			if ws.WindowId == "" {
+				if i < curWsIndex && (freeWsIndexBeforeCurrent == -1 || i > freeWsIndexBeforeCurrent) {
+					freeWsIndexBeforeCurrent = i
+				} else if i > curWsIndex && (freeWsIndexAfterCurrent == -1 || i < freeWsIndexAfterCurrent) {
+					freeWsIndexAfterCurrent = i
+				}
+			}
+		}
+		
+		// Select the closest free workspace
+		var UnclaimedWorkspace string
+		if freeWsIndexBeforeCurrent != -1 && freeWsIndexAfterCurrent != -1 {
+			if curWsIndex-freeWsIndexBeforeCurrent <= freeWsIndexAfterCurrent-curWsIndex {
+				UnclaimedWorkspace = workspaces[freeWsIndexBeforeCurrent].WorkspaceId
+			} else {
+				UnclaimedWorkspace = workspaces[freeWsIndexAfterCurrent].WorkspaceId
+			}
+		} else if freeWsIndexBeforeCurrent != -1 {
+			UnclaimedWorkspace = workspaces[freeWsIndexBeforeCurrent].WorkspaceId
+		} else if freeWsIndexAfterCurrent != -1 {
+			UnclaimedWorkspace = workspaces[freeWsIndexAfterCurrent].WorkspaceId
+		}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d3dca6 and 8c10180.

📒 Files selected for processing (3)
  • emain/emain-window.ts (4 hunks)
  • pkg/wcore/window.go (2 hunks)
  • pkg/wcore/workspace.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • emain/emain-window.ts
  • pkg/wcore/window.go
🧰 Additional context used
🪛 golangci-lint (1.62.2)
pkg/wcore/workspace.go

137-137: ineffectual assignment to err

(ineffassign)

🔇 Additional comments (4)
pkg/wcore/workspace.go (4)

69-70: LGTM: Clean struct initialization.

The addition of a trailing comma in the event struct initialization improves maintainability for future field additions.


118-126: LGTM: Consistent error handling with new return value.

The function signature change and early returns properly handle the new unclaimed workspace ID return value while maintaining robust error handling.


134-134: LGTM: Proper error propagation.

The error handling correctly includes the empty string return value while maintaining clear error context.


137-140: ⚠️ Potential issue

Fix ineffectual error assignment.

The error from DBFindWindowForWorkspaceId is never checked before being overwritten.

Apply this fix:

-	windowId, err := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId)
-	err = wstore.DBDelete(ctx, waveobj.OType_Workspace, workspaceId)
+	windowId, findErr := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId)
+	if findErr != nil {
+		return false, "", fmt.Errorf("error finding window: %w", findErr)
+	}
+	err := wstore.DBDelete(ctx, waveobj.OType_Workspace, workspaceId)

Likely invalid or redundant comment.

🧰 Tools
🪛 golangci-lint (1.62.2)

137-137: ineffectual assignment to err

(ineffassign)

@Jalileh Jalileh requested a review from esimkowitz December 26, 2024 18:30
@Jalileh Jalileh requested a review from esimkowitz December 26, 2024 23:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/wcore/workspace.go (1)

158-175: Improve variable naming for better readability.

The current implementation correctly finds the closest unclaimed workspace as requested, but the naming could be more descriptive.

Consider these naming improvements:

-		UnclaimedWorkspace, findAfter := "", false
+		closestUnclaimedWorkspace, searchAfterCurrent := "", false
 		for i, ws := range workspaces {
 			if workspaceId == ws.WorkspaceId {
 				for N := i; N >= 0; N-- {
 					if workspaces[N].WindowId == "" {
-						UnclaimedWorkspace = workspaces[N].WorkspaceId
+						closestUnclaimedWorkspace = workspaces[N].WorkspaceId
 						break
 					}
 				}
-				if UnclaimedWorkspace != "" {
+				if closestUnclaimedWorkspace != "" {
 					break
 				}
-				findAfter = true
-			} else if findAfter && workspaces[i].WindowId == "" {
-				UnclaimedWorkspace = workspaces[i].WorkspaceId
+				searchAfterCurrent = true
+			} else if searchAfterCurrent && workspaces[i].WindowId == "" {
+				closestUnclaimedWorkspace = workspaces[i].WorkspaceId
 				break
 			}
 		}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a64a1b1 and 267ebdf.

📒 Files selected for processing (2)
  • pkg/wcore/window.go (2 hunks)
  • pkg/wcore/workspace.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/wcore/window.go
🧰 Additional context used
🪛 golangci-lint (1.62.2)
pkg/wcore/workspace.go

146-146: SA4006: this value of err is never used

(staticcheck)

🔇 Additional comments (3)
pkg/wcore/workspace.go (3)

69-70: LGTM!

The addition of a trailing comma in the event struct initialization follows Go conventions and improves Git diff readability.


118-118: LGTM! Function signature change aligns with PR objectives.

The updated signature (bool, string, error) enables returning an unclaimed workspace ID, supporting the new workspace switching behavior.


146-149: ⚠️ Potential issue

Fix ineffectual assignment to err.

The result of DBFindWindowForWorkspaceId is never checked before being overwritten.

Apply this fix:

-	windowId, err := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId)
-	err = wstore.DBDelete(ctx, waveobj.OType_Workspace, workspaceId)
+	windowId, findErr := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId)
+	if findErr != nil {
+		return false, "", fmt.Errorf("error finding window: %w", findErr)
+	}
+	err := wstore.DBDelete(ctx, waveobj.OType_Workspace, workspaceId)

Likely invalid or redundant comment.

🧰 Tools
🪛 golangci-lint (1.62.2)

146-146: SA4006: this value of err is never used

(staticcheck)

Comment on lines 118 to 187
func DeleteWorkspace(ctx context.Context, workspaceId string, force bool) (bool, string, error) {
log.Printf("DeleteWorkspace %s\n", workspaceId)
workspace, err := wstore.DBMustGet[*waveobj.Workspace](ctx, workspaceId)
if err != nil && err.Error() == "not found" {
return true, "", fmt.Errorf("workspace probably already deleted %w", err)
}
// @jalileh list needs to be saved early on i assume
workspaces, err := ListWorkspaces(ctx)
if err != nil {
return false, "", fmt.Errorf("error retrieving workspaceList: %w", err)
}

if err != nil {
return false, fmt.Errorf("error getting workspace: %w", err)
return false, "", fmt.Errorf("error getting workspace: %w", err)
}
if workspace.Name != "" && workspace.Icon != "" && !force && (len(workspace.TabIds) > 0 || len(workspace.PinnedTabIds) > 0) {
log.Printf("Ignoring DeleteWorkspace for workspace %s as it is named\n", workspaceId)
return false, nil
return false, "", nil
}

// delete all pinned and unpinned tabs
for _, tabId := range append(workspace.TabIds, workspace.PinnedTabIds...) {
log.Printf("deleting tab %s\n", tabId)
_, err := DeleteTab(ctx, workspaceId, tabId, false)
if err != nil {
return false, fmt.Errorf("error closing tab: %w", err)
return false, "", fmt.Errorf("error closing tab: %w", err)
}
}
windowId, err := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId)
err = wstore.DBDelete(ctx, waveobj.OType_Workspace, workspaceId)
if err != nil {
return false, fmt.Errorf("error deleting workspace: %w", err)
return false, "", fmt.Errorf("error deleting workspace: %w", err)
}
log.Printf("deleted workspace %s\n", workspaceId)
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_WorkspaceUpdate})
Event: wps.Event_WorkspaceUpdate,
})

if windowId != "" {
err = CloseWindow(ctx, windowId, false)

UnclaimedWorkspace, findAfter := "", false
for i, ws := range workspaces {
if workspaceId == ws.WorkspaceId {
for N := i; N >= 0; N-- {
if workspaces[N].WindowId == "" {
UnclaimedWorkspace = workspaces[N].WorkspaceId
break
}
}
if UnclaimedWorkspace != "" {
break
}
findAfter = true
} else if findAfter && workspaces[i].WindowId == "" {
UnclaimedWorkspace = workspaces[i].WorkspaceId
break
}
}

if UnclaimedWorkspace != "" {
return true, UnclaimedWorkspace, nil
} else {
err = CloseWindow(ctx, windowId, false)
}

if err != nil {
return false, fmt.Errorf("error closing window: %w", err)
return false, "", fmt.Errorf("error closing window: %w", err)
}
}
return true, nil
return true, "", nil
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Missing test coverage for DeleteWorkspace scenarios

The codebase lacks dedicated test coverage for the DeleteWorkspace functionality. The implementation in pkg/wcore/workspace.go handles complex scenarios including:

  • Workspace deletion with state management
  • Window and tab cleanup
  • Unclaimed workspace handling
  • Force deletion behavior

Critical areas needing test coverage:

  • Deleting workspaces with/without unclaimed workspaces before/after
  • Force deletion vs. protected named workspaces
  • Window reassignment logic
  • Tab cleanup during workspace deletion
  • Error handling for various failure scenarios

Note: I've found that while there are some tests for delete operations in other parts of the codebase (like syncmap_test.go and blockstore_test.go), there's no specific test coverage for the DeleteWorkspace functionality. This is concerning given the complexity of the implementation and the various edge cases it handles.

🔗 Analysis chain

Verify the workspace switching behavior with different scenarios.

Please ensure testing covers these scenarios:

  1. Deleting a workspace with unclaimed workspaces before it
  2. Deleting a workspace with unclaimed workspaces after it
  3. Deleting a workspace with no unclaimed workspaces
  4. Deleting the last workspace
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify test coverage for DeleteWorkspace scenarios

# Check for test files covering DeleteWorkspace
echo "Checking for test coverage..."
rg -l "TestDeleteWorkspace" --type go

# Look for existing test scenarios
echo "Checking existing test scenarios..."
rg "func.*Test.*DeleteWorkspace|func.*Test.*Workspace.*Delete" --type go -A 10

Length of output: 268


Script:

#!/bin/bash
# Let's try a broader search for workspace-related tests and deletion functionality

# Search for workspace test files
echo "Searching for workspace test files..."
fd "workspace.*test.*\.go$"

# Search for any test functions related to workspace operations
echo "Searching for workspace test functions..."
rg "func Test.*Workspace" --type go -A 5

# Search for any delete-related test functions
echo "Searching for delete-related test functions..."
rg "func Test.*Delete" --type go -A 5

# Look for actual implementation to understand test coverage needs
echo "Searching for DeleteWorkspace implementation references..."
rg "DeleteWorkspace" --type go -A 2 -B 2

Length of output: 4444

🧰 Tools
🪛 golangci-lint (1.62.2)

146-146: SA4006: this value of err is never used

(staticcheck)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleting workspaces with/without unclaimed workspaces before/after TESTED ✔
Force deletion vs. protected named workspaces ???
Window reassignment logic TESTED WORKS WONDERFULLY ✔
Tab cleanup during workspace deletion TESTED ✔
Error handling for various failure scenarios TESTED/DEBUGGED x4^1000 ✔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jalileh, thanks for clarifying. It looks like everything is tested except for the force deletion of a protected (named) workspace scenario. Below is an example Go test that could help ensure coverage for force deletion vs. protected named workspaces. This test checks whether a named workspace is not deleted without force, but is deleted with force:

package wcore_test

import (
    "context"
    "testing"

    "github.com/stretchr/testify/require"
    "github.com/wavetermdev/waveterm/pkg/wcore"
    "github.com/wavetermdev/waveterm/pkg/waveobj"
    "github.com/wavetermdev/waveterm/pkg/wstore"
)

// TestDeleteWorkspaceForce checks that named, non-empty workspaces
// are only deleted when force is true.
func TestDeleteWorkspaceForce(t *testing.T) {
    ctx := context.Background()

    // Step 1: Create a workspace
    ws, err := wcore.CreateWorkspace(ctx, "MyProtectedWorkspace", "triangle", "#FF9500", false, false)
    require.NoError(t, err, "error creating workspace")

    // Ensure it remains if not forced
    deleted, unclaimed, err := wcore.DeleteWorkspace(ctx, ws.OID, false)
    require.NoError(t, err, "unexpected error calling DeleteWorkspace")
    require.False(t, deleted, "expected not to delete a named, non-empty workspace without force")
    require.Empty(t, unclaimed, "no unclaimed workspace ID should be returned if not deleted")

    // Step 2: Now delete with force
    deleted, unclaimed, err = wcore.DeleteWorkspace(ctx, ws.OID, true)
    require.NoError(t, err, "unexpected error calling DeleteWorkspace with force=true")
    require.True(t, deleted, "expected workspace to be deleted when forced")
    require.Empty(t, unclaimed, "no unclaimed workspace ID should be returned since we forced deletion")

    // Verify the workspace is gone
    storedWS, getErr := wstore.DBGet[*waveobj.Workspace](ctx, ws.OID)
    require.Nil(t, storedWS, "workspace should no longer exist after deletion")
    require.NoError(t, getErr, "should not produce an error if workspace is truly gone")
}

This example:

  1. Creates a named workspace.
  2. Attempts to delete it without force, verifying it fails.
  3. Attempts again with force=true, ensuring that it is deleted.

Let me know if you have any further questions!


✏️ Learnings added
Learnt from: Jalileh
PR: wavetermdev/waveterm#1623
File: pkg/wcore/workspace.go:0-0
Timestamp: 2024-12-27T22:06:04.392Z
Learning: We should confirm test coverage for "force deletion vs. protected named workspaces" in the workspace tests (e.g., verifying that named, non-empty workspaces cannot be deleted without force).

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think I want to talk to you anymore

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jalileh, I understand your decision. If you do have any requests in the future, I’ll be here to help. Take care.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
pkg/wcore/workspace.go (3)

118-118: Update function documentation to reflect new return values.

The function comment should document the new string return value that represents the unclaimed workspace ID.

-// Returns true if workspace was deleted, false if it was not deleted.
+// Returns:
+// - bool: true if workspace was deleted, false if it was not deleted
+// - string: ID of an unclaimed workspace if available, empty string otherwise
+// - error: any error that occurred during the operation

121-128: Improve error handling and remove TODO comment.

  1. The error handling could be more idiomatic
  2. The TODO comment about list saving should be removed
-	if err != nil && wstore.ErrNotFound == err {
-		return true, "", fmt.Errorf("workspace already deleted %w", err)
-	}
-	// @jalileh list needs to be saved early on i assume
-	workspaces, err := ListWorkspaces(ctx)
-	if err != nil {
-		return false, "", fmt.Errorf("error retrieving workspaceList: %w", err)
-	}
+	if err != nil {
+		if wstore.ErrNotFound == err {
+			return true, "", nil // Already deleted is not an error condition
+		}
+		return false, "", fmt.Errorf("error getting workspace: %w", err)
+	}
+	
+	workspaces, err := ListWorkspaces(ctx)
+	if err != nil {
+		return false, "", fmt.Errorf("error retrieving workspace list: %w", err)
+	}

158-175: Improve readability of workspace switching logic.

While the logic is correct, the code could be more readable with better variable naming and documentation.

-		UnclaimedWorkspace, findAfter := "", false
+		// First try to find an unclaimed workspace before the current one,
+		// then look after if none is found
+		candidateWorkspaceId, searchAfterCurrent := "", false
 		for i, ws := range workspaces {
 			if workspaceId == ws.WorkspaceId {
 				for N := i; N >= 0; N-- {
 					if workspaces[N].WindowId == "" {
-						UnclaimedWorkspace = workspaces[N].WorkspaceId
+						candidateWorkspaceId = workspaces[N].WorkspaceId
 						break
 					}
 				}
-				if UnclaimedWorkspace != "" {
+				if candidateWorkspaceId != "" {
 					break
 				}
-				findAfter = true
-			} else if findAfter && workspaces[i].WindowId == "" {
-				UnclaimedWorkspace = workspaces[i].WorkspaceId
+				searchAfterCurrent = true
+			} else if searchAfterCurrent && workspaces[i].WindowId == "" {
+				candidateWorkspaceId = workspaces[i].WorkspaceId
 				break
 			}
 		}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 267ebdf and 190ff4b.

📒 Files selected for processing (1)
  • pkg/wcore/workspace.go (2 hunks)
🧰 Additional context used
🪛 golangci-lint (1.62.2)
pkg/wcore/workspace.go

146-146: ineffectual assignment to err

(ineffassign)

🔇 Additional comments (3)
pkg/wcore/workspace.go (3)

69-70: LGTM! Event publishing changes look good.

The addition of trailing commas in event struct initialization improves consistency.

Also applies to: 153-154


146-149: ⚠️ Potential issue

Fix ineffectual error assignment.

The error from DBFindWindowForWorkspaceId is never checked before being overwritten.

 	windowId, err := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId)
+	if err != nil {
+		return false, "", fmt.Errorf("error finding window: %w", err)
+	}
 	err = wstore.DBDelete(ctx, waveobj.OType_Workspace, workspaceId)
 	if err != nil {
 		return false, "", fmt.Errorf("error deleting workspace: %w", err)
 	}

Likely invalid or redundant comment.

🧰 Tools
🪛 golangci-lint (1.62.2)

146-146: ineffectual assignment to err

(ineffassign)


157-187: Verify workspace switching behavior.

Let's verify that the implementation correctly handles all scenarios for workspace switching.

✅ Verification successful

Workspace switching implementation is correctly implemented

The implementation correctly handles workspace switching with proper fallback behavior:

  1. When deleting a workspace, it first searches for an unclaimed workspace to switch to:

    • Looks backward from the current workspace position for any unclaimed workspace
    • If none found before, looks forward from the current position
    • Only closes the window if no unclaimed workspace is available
  2. The implementation includes proper error handling:

    • Workspace existence verification
    • Window closure error handling
    • Proper cleanup of tabs and resources
  3. The switching logic is well-integrated with the window management system through CloseWindow and workspace store operations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify workspace switching implementation

# Check for proper error handling in workspace switching
echo "Checking error handling in workspace switching..."
rg "DeleteWorkspace.*return.*false.*string.*error" --type go

# Verify window closure only happens when no unclaimed workspaces exist
echo "Checking window closure conditions..."
rg "CloseWindow.*ctx.*windowId.*false" -B 5 --type go

# Look for test coverage of the new functionality
echo "Checking test coverage..."
rg "TestDeleteWorkspace|TestWorkspaceSwitching" --type go

Length of output: 1502


Script:

#!/bin/bash
# Let's verify the workspace switching logic more comprehensively

# Check the complete DeleteWorkspace function implementation
echo "Checking DeleteWorkspace implementation..."
ast-grep --pattern 'func DeleteWorkspace(ctx context.Context, $$$) ($_, $_, error) {
  $$$
}'

# Look for workspace switching related functions
echo "Checking workspace-related functions..."
rg "func.*Workspace" --type go -A 1

# Check for workspace store operations
echo "Checking workspace store operations..."
rg "DBFindWorkspaceById|DBListWorkspaces" --type go -A 2

Length of output: 13225

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
pkg/wcore/workspace.go (1)

158-173: Consider optimizing the workspace switching logic.

The current implementation uses a two-pass approach to find unclaimed workspaces. This could be optimized to a single pass by tracking both before and after unclaimed workspaces simultaneously.

-		UnclaimedWorkspace, findAfter := "", false
-		for _, ws := range workspaces {
-			if ws.WorkspaceId == workspaceId {
-				if UnclaimedWorkspace != "" {
-					break
-				}
-				findAfter = true
-				continue
-			}
-			if findAfter && ws.WindowId == "" {
-				UnclaimedWorkspace = ws.WorkspaceId
-				break
-			} else if ws.WindowId == "" {
-				UnclaimedWorkspace = ws.WorkspaceId
-			}
-		}
+		var beforeCurrent, afterCurrent string
+		var foundCurrent bool
+		for _, ws := range workspaces {
+			if ws.WindowId == "" {
+				if !foundCurrent {
+					beforeCurrent = ws.WorkspaceId
+				} else {
+					afterCurrent = ws.WorkspaceId
+					break
+				}
+			}
+			if ws.WorkspaceId == workspaceId {
+				foundCurrent = true
+			}
+		}
+		UnclaimedWorkspace := afterCurrent
+		if UnclaimedWorkspace == "" {
+			UnclaimedWorkspace = beforeCurrent
+		}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 190ff4b and 59b8d28.

📒 Files selected for processing (1)
  • pkg/wcore/workspace.go (2 hunks)
🧰 Additional context used
📓 Learnings (1)
pkg/wcore/workspace.go (1)
Learnt from: Jalileh
PR: wavetermdev/waveterm#1623
File: pkg/wcore/workspace.go:0-0
Timestamp: 2024-12-27T22:06:04.948Z
Learning: We should confirm test coverage for "force deletion vs. protected named workspaces" in the workspace tests (e.g., verifying that named, non-empty workspaces cannot be deleted without force).
🪛 golangci-lint (1.62.2)
pkg/wcore/workspace.go

146-146: ineffectual assignment to err

(ineffassign)

🔇 Additional comments (4)
pkg/wcore/workspace.go (4)

69-70: LGTM: Event publishing format is consistent.

The trailing comma in the event struct initialization improves maintainability.


118-118: LGTM: Improved error handling for deleted workspaces.

The function now properly handles the case where the workspace is already deleted, returning appropriate values.

Also applies to: 121-123


146-149: ⚠️ Potential issue

Fix ineffectual error assignment.

The error from DBFindWindowForWorkspaceId is assigned but never checked before being overwritten.

-	windowId, err := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId)
-	err = wstore.DBDelete(ctx, waveobj.OType_Workspace, workspaceId)
+	windowId, _ := wstore.DBFindWindowForWorkspaceId(ctx, workspaceId)
+	err := wstore.DBDelete(ctx, waveobj.OType_Workspace, workspaceId)

Likely invalid or redundant comment.

🧰 Tools
🪛 golangci-lint (1.62.2)

146-146: ineffectual assignment to err

(ineffassign)


133-135: 💡 Codebase verification

Missing test coverage for force deletion scenarios in named workspaces

Based on the code inspection, while the implementation correctly handles force deletion logic for named workspaces, there appears to be no dedicated test coverage for these critical scenarios. The implementation shows:

  1. Named workspaces with content are protected from deletion unless force=true
  2. Empty named workspaces can be deleted regardless of force flag
  3. Force flag bypasses the protection for named workspaces

Key locations requiring test coverage:

  • pkg/wcore/workspace.go: Implementation of DeleteWorkspace function
  • Missing test file that should cover:
    • Deletion attempt on named workspace with content (should fail)
    • Force deletion of named workspace with content (should succeed)
    • Deletion of empty named workspace (should succeed)

This aligns with the previously learned concern about ensuring test coverage for "force deletion vs. protected named workspaces."

🔗 Analysis chain

Verify force deletion behavior for named workspaces.

The implementation correctly handles force deletion of named workspaces, but ensure test coverage exists for these scenarios:

  1. Named workspace with content (should not delete without force)
  2. Named workspace with force flag (should delete)
  3. Empty named workspace (should delete regardless of force flag)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check test coverage for force deletion scenarios

# Look for test cases covering force deletion
rg -A 10 "TestDelete.*Workspace.*force" --type go

# Check implementation references
rg -A 5 "DeleteWorkspace.*force.*true" --type go

Length of output: 102


Script:

#!/bin/bash
# Let's try a broader search for workspace deletion tests and force flag usage

# Search for workspace deletion tests
rg -l "func.*Test.*Workspace.*Delete" --type go

# Search for force flag usage in workspace tests
rg "force.*=.*true" --type go ./pkg/wcore/

# Look for workspace deletion test cases
ast-grep --pattern 'func Test$_(t *testing.T) {
  $$$
  DeleteWorkspace($$$)
  $$$
}'

Length of output: 182


Script:

#!/bin/bash
# Let's try a different approach to find test files and workspace deletion logic

# Find test files in pkg/wcore
fd ".*_test.go" pkg/wcore/

# Look for workspace-related test files
rg -l "workspace" pkg/wcore/ --type go | grep "_test"

# Check the actual implementation for force deletion logic
rg -A 5 -B 5 "DeleteWorkspace" pkg/wcore/workspace.go

Length of output: 1181

⛔ Skipped due to learnings
Learnt from: Jalileh
PR: wavetermdev/waveterm#1623
File: pkg/wcore/workspace.go:0-0
Timestamp: 2024-12-27T22:06:04.948Z
Learning: We should confirm test coverage for "force deletion vs. protected named workspaces" in the workspace tests (e.g., verifying that named, non-empty workspaces cannot be deleted without force).

@esimkowitz
Copy link
Member

Just tested, it works great! I've submitted a small formatting fix for the TypeScript files, I'm guessing you don't have Prettier set up for your Neovim. I'm going to sign off on this, feel free to merge it when you're ready 😃

Thank you so much for your contribution!!

@esimkowitz esimkowitz merged commit 0890475 into wavetermdev:main Dec 29, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants