feat: close clusters locally from tui
This commit is contained in:
parent
ba175deae5
commit
cce785f371
@ -1060,6 +1060,12 @@ func (m *clusterBrowserModel) openActionMenu() {
|
||||
tuiMenuItem{label: "Copy cluster title", action: "copy-cluster-title"},
|
||||
tuiMenuItem{label: "Copy cluster summary", action: "copy-cluster"},
|
||||
)
|
||||
cluster, _ := m.selectedCluster()
|
||||
if cluster.Status == "closed" || cluster.ClosedAt != "" {
|
||||
m.menuItems = append(m.menuItems, tuiMenuItem{label: "Reopen cluster locally...", action: "reopen-cluster-confirm"})
|
||||
} else {
|
||||
m.menuItems = append(m.menuItems, tuiMenuItem{label: "Close cluster locally...", action: "close-cluster-confirm"})
|
||||
}
|
||||
if m.hasDetail {
|
||||
m.menuItems = append(m.menuItems, tuiMenuItem{label: "Copy member list", action: "copy-member-list"})
|
||||
}
|
||||
@ -1262,6 +1268,18 @@ func (m *clusterBrowserModel) runMenuItem(item tuiMenuItem) bool {
|
||||
m.status = "Copied representative URL"
|
||||
}
|
||||
return true
|
||||
case "close-cluster-confirm":
|
||||
m.openCloseClusterMenu()
|
||||
return false
|
||||
case "close-cluster-local":
|
||||
m.closeSelectedClusterLocally()
|
||||
return true
|
||||
case "reopen-cluster-confirm":
|
||||
m.openReopenClusterMenu()
|
||||
return false
|
||||
case "reopen-cluster-local":
|
||||
m.reopenSelectedClusterLocally()
|
||||
return true
|
||||
case "load-neighbors":
|
||||
m.loadSelectedThreadNeighbors(10, 0.2)
|
||||
return true
|
||||
@ -1643,6 +1661,38 @@ func (m *clusterBrowserModel) openReopenThreadMenu() {
|
||||
m.status = fmt.Sprintf("Confirm local reopen for #%d", thread.Number)
|
||||
}
|
||||
|
||||
func (m *clusterBrowserModel) openCloseClusterMenu() {
|
||||
cluster, ok := m.selectedCluster()
|
||||
if !ok {
|
||||
m.status = "No selected cluster"
|
||||
return
|
||||
}
|
||||
m.menuTitle = "Close Cluster"
|
||||
m.menuItems = []tuiMenuItem{
|
||||
{label: fmt.Sprintf("Close cluster C%d locally", cluster.ID), action: "close-cluster-local"},
|
||||
{label: "Back to actions", action: "back-to-actions"},
|
||||
}
|
||||
m.menuIndex = 0
|
||||
m.menuOff = 0
|
||||
m.status = fmt.Sprintf("Confirm local close for cluster C%d", cluster.ID)
|
||||
}
|
||||
|
||||
func (m *clusterBrowserModel) openReopenClusterMenu() {
|
||||
cluster, ok := m.selectedCluster()
|
||||
if !ok {
|
||||
m.status = "No selected cluster"
|
||||
return
|
||||
}
|
||||
m.menuTitle = "Reopen Cluster"
|
||||
m.menuItems = []tuiMenuItem{
|
||||
{label: fmt.Sprintf("Reopen cluster C%d locally", cluster.ID), action: "reopen-cluster-local"},
|
||||
{label: "Back to actions", action: "back-to-actions"},
|
||||
}
|
||||
m.menuIndex = 0
|
||||
m.menuOff = 0
|
||||
m.status = fmt.Sprintf("Confirm local reopen for cluster C%d", cluster.ID)
|
||||
}
|
||||
|
||||
func (m *clusterBrowserModel) closeSelectedThreadLocally() {
|
||||
thread, ok := m.selectedThread()
|
||||
if !ok {
|
||||
@ -1680,6 +1730,42 @@ func (m *clusterBrowserModel) reopenSelectedThreadLocally() {
|
||||
m.status = fmt.Sprintf("Reopened #%d locally", thread.Number)
|
||||
}
|
||||
|
||||
func (m *clusterBrowserModel) closeSelectedClusterLocally() {
|
||||
cluster, ok := m.selectedCluster()
|
||||
if !ok {
|
||||
m.status = "No selected cluster"
|
||||
return
|
||||
}
|
||||
if m.store == nil || m.repoID == 0 {
|
||||
m.status = "Local cluster close unavailable for this view"
|
||||
return
|
||||
}
|
||||
if err := m.store.CloseClusterLocally(m.ctx, m.repoID, cluster.ID, "TUI manual close"); err != nil {
|
||||
m.status = err.Error()
|
||||
return
|
||||
}
|
||||
m.refreshFromStore()
|
||||
m.status = fmt.Sprintf("Closed cluster C%d locally", cluster.ID)
|
||||
}
|
||||
|
||||
func (m *clusterBrowserModel) reopenSelectedClusterLocally() {
|
||||
cluster, ok := m.selectedCluster()
|
||||
if !ok {
|
||||
m.status = "No selected cluster"
|
||||
return
|
||||
}
|
||||
if m.store == nil || m.repoID == 0 {
|
||||
m.status = "Local cluster reopen unavailable for this view"
|
||||
return
|
||||
}
|
||||
if err := m.store.ReopenClusterLocally(m.ctx, m.repoID, cluster.ID); err != nil {
|
||||
m.status = err.Error()
|
||||
return
|
||||
}
|
||||
m.refreshFromStore()
|
||||
m.status = fmt.Sprintf("Reopened cluster C%d locally", cluster.ID)
|
||||
}
|
||||
|
||||
func (m clusterBrowserModel) menuVisibleCount() int {
|
||||
height := m.detailView.Height
|
||||
if height <= 0 {
|
||||
|
||||
@ -1419,6 +1419,105 @@ func TestTUIReopenThreadLocallyRestoresThread(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUICloseClusterLocallyHidesCluster(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, err := store.Open(ctx, filepath.Join(t.TempDir(), "gitcrawl.db"))
|
||||
if err != nil {
|
||||
t.Fatalf("open store: %v", err)
|
||||
}
|
||||
defer st.Close()
|
||||
|
||||
repoID, err := st.UpsertRepository(ctx, store.Repository{Owner: "openclaw", Name: "openclaw", FullName: "openclaw/openclaw", RawJSON: "{}", UpdatedAt: "2026-04-27T00:00:00Z"})
|
||||
if err != nil {
|
||||
t.Fatalf("repo: %v", err)
|
||||
}
|
||||
if err := seedTUICluster(ctx, st, repoID, 52, 502, "close cluster"); err != nil {
|
||||
t.Fatalf("seed cluster: %v", err)
|
||||
}
|
||||
clusters, err := st.ListClusterSummaries(ctx, store.ClusterSummaryOptions{RepoID: repoID, IncludeClosed: false, MinSize: 1, Limit: 20, Sort: "recent"})
|
||||
if err != nil {
|
||||
t.Fatalf("clusters: %v", err)
|
||||
}
|
||||
model := newClusterBrowserModel(ctx, st, repoID, clusterBrowserPayload{
|
||||
Repository: "openclaw/openclaw",
|
||||
Sort: "recent",
|
||||
HideClosed: true,
|
||||
MinSize: 1,
|
||||
Clusters: clusters,
|
||||
})
|
||||
model.openActionMenu()
|
||||
if menuLabelIndex(model.menuItems, "Close cluster locally...") < 0 {
|
||||
t.Fatalf("action menu missing cluster close: %+v", model.menuItems)
|
||||
}
|
||||
model.runAction("close-cluster-confirm")
|
||||
if model.menuTitle != "Close Cluster" || !strings.Contains(model.menuItems[0].label, "Close cluster C52 locally") {
|
||||
t.Fatalf("close cluster confirmation menu = %q %+v", model.menuTitle, model.menuItems)
|
||||
}
|
||||
|
||||
model.runAction("close-cluster-local")
|
||||
|
||||
if model.status != "Closed cluster C52 locally" {
|
||||
t.Fatalf("close cluster status = %q", model.status)
|
||||
}
|
||||
if len(model.payload.Clusters) != 0 {
|
||||
t.Fatalf("locally closed cluster should be hidden, got %#v", model.payload.Clusters)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUIReopenClusterLocallyRestoresCluster(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, err := store.Open(ctx, filepath.Join(t.TempDir(), "gitcrawl.db"))
|
||||
if err != nil {
|
||||
t.Fatalf("open store: %v", err)
|
||||
}
|
||||
defer st.Close()
|
||||
|
||||
repoID, err := st.UpsertRepository(ctx, store.Repository{Owner: "openclaw", Name: "openclaw", FullName: "openclaw/openclaw", RawJSON: "{}", UpdatedAt: "2026-04-27T00:00:00Z"})
|
||||
if err != nil {
|
||||
t.Fatalf("repo: %v", err)
|
||||
}
|
||||
if err := seedTUICluster(ctx, st, repoID, 53, 503, "reopen cluster"); err != nil {
|
||||
t.Fatalf("seed cluster: %v", err)
|
||||
}
|
||||
if err := st.CloseClusterLocally(ctx, repoID, 53, "test close"); err != nil {
|
||||
t.Fatalf("close cluster: %v", err)
|
||||
}
|
||||
clusters, err := st.ListClusterSummaries(ctx, store.ClusterSummaryOptions{RepoID: repoID, IncludeClosed: true, MinSize: 1, Limit: 20, Sort: "recent"})
|
||||
if err != nil {
|
||||
t.Fatalf("clusters: %v", err)
|
||||
}
|
||||
model := newClusterBrowserModel(ctx, st, repoID, clusterBrowserPayload{
|
||||
Repository: "openclaw/openclaw",
|
||||
Sort: "recent",
|
||||
MinSize: 1,
|
||||
Clusters: clusters,
|
||||
})
|
||||
model.openActionMenu()
|
||||
if menuLabelIndex(model.menuItems, "Reopen cluster locally...") < 0 {
|
||||
t.Fatalf("action menu missing cluster reopen: %+v", model.menuItems)
|
||||
}
|
||||
if menuLabelIndex(model.menuItems, "Close cluster locally...") >= 0 {
|
||||
t.Fatalf("closed cluster should not offer close again: %+v", model.menuItems)
|
||||
}
|
||||
model.runAction("reopen-cluster-confirm")
|
||||
if model.menuTitle != "Reopen Cluster" || !strings.Contains(model.menuItems[0].label, "Reopen cluster C53 locally") {
|
||||
t.Fatalf("reopen cluster confirmation menu = %q %+v", model.menuTitle, model.menuItems)
|
||||
}
|
||||
|
||||
model.runAction("reopen-cluster-local")
|
||||
|
||||
if model.status != "Reopened cluster C53 locally" {
|
||||
t.Fatalf("reopen cluster status = %q", model.status)
|
||||
}
|
||||
clusters, err = st.ListClusterSummaries(ctx, store.ClusterSummaryOptions{RepoID: repoID, IncludeClosed: false, MinSize: 1, Limit: 20, Sort: "recent"})
|
||||
if err != nil {
|
||||
t.Fatalf("list reopened clusters: %v", err)
|
||||
}
|
||||
if len(clusters) != 1 || clusters[0].ClosedAt != "" {
|
||||
t.Fatalf("reopened cluster should be visible, got %#v", clusters)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTUIRepositoryPickerKeepsCurrentRepoVisible(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
st, err := store.Open(ctx, filepath.Join(t.TempDir(), "gitcrawl.db"))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user