Skip to content
This repository has been archived by the owner on Jan 19, 2023. It is now read-only.

Commit

Permalink
Added dropdowns to breadcrumbs
Browse files Browse the repository at this point in the history
Signed-off-by: Milan Klanjsek <[email protected]>

Added changelog file

Signed-off-by: Milan Klanjsek <[email protected]>

Added dropdown overflow logic

Signed-off-by: Milan Klanjsek <[email protected]>

Illustrate using breadcrumbs from plugins, fix for component unmarshal issue
Added dropdown to list of docs generating components, get rid of console warning

Signed-off-by: Milan Klanjsek <[email protected]>

Switch to use module navigation data

Signed-off-by: Milan Klanjsek <[email protected]>

Removed old breadcrumbs code and RootPath logic

Signed-off-by: Milan Klanjsek <[email protected]>

Code review feedback

Signed-off-by: Milan Klanjsek <[email protected]>
  • Loading branch information
mklanjsek committed Jan 6, 2021
1 parent 31124bf commit 056611e
Show file tree
Hide file tree
Showing 52 changed files with 1,291 additions and 294 deletions.
1 change: 1 addition & 0 deletions changelogs/unreleased/1212-mklanjsek
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added dropdowns to breadcrumbs
14 changes: 13 additions & 1 deletion cmd/octant-sample-plugin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,19 @@ func initRoutes(router *service.Router) {
component1 := gen("Tab 1", "tab1", request.Path())
component2 := gen("Tab 2", "tab2", request.Path())

contentResponse := component.NewContentResponse(component.TitleFromString("Example"))
// Illustrate using dropdowns and links for breadcrumbs
items := make([]component.DropdownItemConfig, 0)
dropdown := component.NewDropdown("test", component.DropdownLink, "action", items...)
dropdown.AddDropdownItem("first", component.Url, "Nested Once", "nested-once", "")
dropdown.AddDropdownItem("second", component.Url, "Nested Twice", "nested-once/nested-twice", "")
dropdown.SetTitle(append([]component.TitleComponent{}, component.NewLink("", "Dropdown", "/url")))

var title []component.TitleComponent
title = component.Title(dropdown)
title = append(title, component.NewLink("", "Example Link", "link"))
title = append(title, component.NewText("Example"))

contentResponse := component.NewContentResponse(title)
contentResponse.Add(component1, component2)

return *contentResponse, nil
Expand Down
153 changes: 153 additions & 0 deletions internal/api/breadcrumb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2020 the Octant contributors. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package api

import (
"path"
"sort"
"strings"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"github.com/vmware-tanzu/octant/internal/module"
"github.com/vmware-tanzu/octant/internal/octant"
"github.com/vmware-tanzu/octant/pkg/store"
"github.com/vmware-tanzu/octant/pkg/view/component"

"github.com/vmware-tanzu/octant/pkg/navigation"
)

type LinkDefinition struct {
Title string
Url string
}

// Generate breadcrumb for specified path
func GenerateBreadcrumb(cm *ContentManager, contentPath string, state octant.State, m module.Module, options module.ContentOptions) []component.TitleComponent {
var title []component.TitleComponent
crPath := "custom-resources"

navs, err := cm.moduleManager.Navigation(cm.ctx, state.GetNamespace(), m.Name())
if err != nil {
return title
}

parent, title := CreateNavigationBreadcrumb(navs, contentPath)
if title == nil {
return title
}

if strings.Contains(contentPath, crPath) {
if path.Base(contentPath) == parent.Title || path.Base(parent.Url) == crPath {
title = append(title, component.NewText(parent.Title))
} else {
title = append(title, component.NewLink("", parent.Title, parent.Url), component.NewText(path.Base(contentPath)))
}
} else {
gvk, err := cm.moduleManager.GvkFromPath(path.Dir(contentPath), state.GetNamespace())
if err != nil {
title = append(title, component.NewText(parent.Title))
return title
}
key := store.KeyFromGroupVersionKind(gvk)
key.Selector = options.LabelSet

if !isClusterScoped(m) {
key.Namespace = state.GetNamespace()
}
list, _, err := cm.dashConfig.ObjectStore().List(cm.ctx, key)
if err == nil && list.Items != nil && len(list.Items) > 0 {
second := dropdownFromList(parent, path.Base(contentPath), list)
title = append(title, second, component.NewText(path.Base(contentPath)))
} else {
title = append(title, component.NewText(parent.Title))
}
}
return title
}

// Create first part of breadcrumb from module navigation entries.
// Performs reverse path traversal and creates all related breadcrumbs.
func CreateNavigationBreadcrumb(navs []navigation.Navigation, contentPath string) (LinkDefinition, []component.TitleComponent) {
var last LinkDefinition
var title []component.TitleComponent

thisPath := contentPath
for {
if thisPath == "." { // done
break
}
navItems, parent, selection := NavigationFromPath(navs, thisPath)
if len(navItems) > 0 {
dropdown := dropdownFromNavigation(parent, selection.Title, navItems)
title = append(title, dropdown)
if len(last.Title) == 0 {
last = LinkDefinition{Title: selection.Title, Url: selection.Url}
}
}
thisPath = path.Dir(thisPath)
}
return last, reverseTitle(title)
}

// Returns all navigation elements for specified path
func NavigationFromPath(navs []navigation.Navigation, navPath string) ([]navigation.Navigation, LinkDefinition, LinkDefinition) {
for _, nav := range navs {
for _, child := range nav.Children {
if child.Path == navPath {
return nav.Children, LinkDefinition{Title: nav.Title, Url: nav.Path}, LinkDefinition{Title: child.Title, Url: child.Path}
}
}
if nav.Path == navPath && nav.Title != navs[0].Title {
return navs, LinkDefinition{Title: navs[0].Title, Url: navs[0].Path}, LinkDefinition{Title: nav.Title, Url: nav.Path}
}
}
return []navigation.Navigation{}, LinkDefinition{}, LinkDefinition{}
}

func dropdownFromNavigation(title LinkDefinition, selection string, items []navigation.Navigation) *component.Dropdown {
dropItems := make([]component.DropdownItemConfig, 0)
for _, item := range items {
item := component.NewDropdownItem(item.Title, component.Url, item.Title, item.Path, "")
dropItems = append(dropItems, item)
}

return createLinkDropdown(title, selection, dropItems, false)
}

func dropdownFromList(title LinkDefinition, selection string, items *unstructured.UnstructuredList) *component.Dropdown {
dropItems := make([]component.DropdownItemConfig, 0)
for _, item := range items.Items {
item := component.NewDropdownItem(item.GetName(), component.Url, item.GetName(),
path.Join(title.Url, item.GetName()), "")
dropItems = append(dropItems, item)
}

return createLinkDropdown(title, selection, dropItems, true)
}

func createLinkDropdown(title LinkDefinition, selection string, items []component.DropdownItemConfig, sortItems bool) *component.Dropdown {
if sortItems {
sort.Slice(items, func(i, j int) bool { return items[i].Label < items[j].Label })
}

dropdown := component.NewDropdown(title.Title, component.DropdownLink, "", items...)
dropdown.SetTitle(append([]component.TitleComponent{}, component.NewLink("", title.Title, title.Url)))
dropdown.SetSelection(selection)
return dropdown
}

func reverseTitle(title []component.TitleComponent) []component.TitleComponent {

for i, j := 0, len(title)-1; i < j; i, j = i+1, j-1 {
title[i], title[j] = title[j], title[i]
}
return title
}

func isClusterScoped(m module.Module) bool {
return m.Name() == "cluster-overview"
}
Loading

0 comments on commit 056611e

Please sign in to comment.