Repository: vmware-tanzu/octant
Pull request #1083 (2020-07-08 20:38:19)

Pin node version to 10.x
**What this PR does / why we need it**:

Use the version of Node we want, rather than whatever happens to be on the runner.

**Release note**:
```
none
```



Patch:
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index 429cda53..997a57a0 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -26,6 +26,8 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/setup-node@v2.0.0
+        with:
+          node-version: 10.x
       - uses: actions/checkout@v2
       - name: eslint
         run: |
diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml
index 640be046..ba72ead1 100644
--- a/.github/workflows/nightly.yaml
+++ b/.github/workflows/nightly.yaml
@@ -8,6 +8,8 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/setup-node@v2.0.0
+        with:
+          node-version: 10.x
       - uses: actions/setup-go@v2
         with:
           go-version: '1.14'
diff --git a/.github/workflows/preflight-checks.yaml b/.github/workflows/preflight-checks.yaml
index d19f8082..152ac3fa 100644
--- a/.github/workflows/preflight-checks.yaml
+++ b/.github/workflows/preflight-checks.yaml
@@ -54,6 +54,8 @@ jobs:
     name: Karma tests on ${{ matrix.platform }}
     steps:
       - uses: actions/setup-node@v2.0.0
+        with:
+          node-version: 10.x
       - uses: actions/checkout@v2
       - name: run_karma
         run: |
@@ -72,6 +74,8 @@ jobs:
     name: Build on ${{ matrix.platform }}
     steps:
       - uses: actions/setup-node@v2.0.0
+        with:
+          node-version: 10.x
       - uses: actions/setup-go@v2
         with:
           go-version: ${{ matrix.go-version }}
diff --git a/changelogs/unreleased/1007-scothis b/changelogs/unreleased/1007-scothis
new file mode 100644
index 00000000..eaeaeafe
--- /dev/null
+++ b/changelogs/unreleased/1007-scothis
@@ -0,0 +1,1 @@
+Normalize navigation bar flyouts when collapsed
diff --git a/internal/modules/clusteroverview/clusteroverview.go b/internal/modules/clusteroverview/clusteroverview.go
index 68c71d87..bbeca328 100644
--- a/internal/modules/clusteroverview/clusteroverview.go
+++ b/internal/modules/clusteroverview/clusteroverview.go
@@ -249,7 +249,7 @@ func (co *ClusterOverview) Generators() []octant.Generator {
 
 func rbacEntries(ctx context.Context, prefix, namespace string, objectStore store.Store, _ bool) ([]navigation.Navigation, bool, error) {
 	neh := navigation.EntriesHelper{}
-	neh.Add("Overview", "", false)
+
 	neh.Add("Cluster Roles", "cluster-roles",
 		loading.IsObjectLoading(ctx, namespace, store.KeyFromGroupVersionKind(gvk.ClusterRole), objectStore))
 	neh.Add("Cluster Role Bindings", "cluster-role-bindings",
@@ -265,7 +265,7 @@ func rbacEntries(ctx context.Context, prefix, namespace string, objectStore stor
 
 func storageEntries(ctx context.Context, prefix, namespace string, objectStore store.Store, _ bool) ([]navigation.Navigation, bool, error) {
 	neh := navigation.EntriesHelper{}
-	neh.Add("Overview", "", false)
+
 	neh.Add("Persistent Volumes", "persistent-volumes",
 		loading.IsObjectLoading(ctx, namespace, store.KeyFromGroupVersionKind(gvk.PersistentVolume), objectStore))
 
diff --git a/internal/modules/overview/navigation.go b/internal/modules/overview/navigation.go
index 5363e135..0d54d042 100644
--- a/internal/modules/overview/navigation.go
+++ b/internal/modules/overview/navigation.go
@@ -28,7 +28,6 @@ var (
 func workloadEntries(ctx context.Context, prefix, namespace string, objectStore store.Store, _ bool) ([]navigation.Navigation, bool, error) {
 	neh := navigation.EntriesHelper{}
 
-	neh.Add("Overview", "", false)
 	neh.Add("Cron Jobs", "cron-jobs",
 		loading.IsObjectLoading(ctx, namespace, store.KeyFromGroupVersionKind(gvk.CronJob), objectStore))
 	neh.Add("Daemon Sets", "daemon-sets",
@@ -57,7 +56,7 @@ func workloadEntries(ctx context.Context, prefix, namespace string, objectStore
 
 func discoAndLBEntries(ctx context.Context, prefix, namespace string, objectStore store.Store, _ bool) ([]navigation.Navigation, bool, error) {
 	neh := navigation.EntriesHelper{}
-	neh.Add("Overview", "", false)
+
 	neh.Add("Horizontal Pod Autoscalers", "horizontal-pod-autoscalers",
 		loading.IsObjectLoading(ctx, namespace, store.KeyFromGroupVersionKind(gvk.HorizontalPodAutoscaler), objectStore))
 	neh.Add("Ingresses", "ingresses",
@@ -78,7 +77,6 @@ func discoAndLBEntries(ctx context.Context, prefix, namespace string, objectStor
 func configAndStorageEntries(ctx context.Context, prefix, namespace string, objectStore store.Store, _ bool) ([]navigation.Navigation, bool, error) {
 	neh := navigation.EntriesHelper{}
 
-	neh.Add("Overview", "", false)
 	neh.Add("Config Maps", "config-maps",
 		loading.IsObjectLoading(ctx, namespace, store.KeyFromGroupVersionKind(gvk.ConfigMap), objectStore))
 	neh.Add("Persistent Volume Claims", "persistent-volume-claims",
@@ -99,7 +97,6 @@ func configAndStorageEntries(ctx context.Context, prefix, namespace string, obje
 func rbacEntries(ctx context.Context, prefix, namespace string, objectStore store.Store, _ bool) ([]navigation.Navigation, bool, error) {
 	neh := navigation.EntriesHelper{}
 
-	neh.Add("Overview", "", false)
 	neh.Add("Roles", "roles",
 		loading.IsObjectLoading(ctx, namespace, store.KeyFromGroupVersionKind(gvk.Role), objectStore))
 	neh.Add("Role Bindings", "role-bindings",
diff --git a/pkg/navigation/navigation.go b/pkg/navigation/navigation.go
index 93d8033a..1b43e851 100644
--- a/pkg/navigation/navigation.go
+++ b/pkg/navigation/navigation.go
@@ -9,7 +9,6 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"github.com/vmware-tanzu/octant/pkg/icon"
 	"path"
 	"sort"
 
@@ -20,6 +19,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 
 	"github.com/vmware-tanzu/octant/internal/log"
+	"github.com/vmware-tanzu/octant/pkg/icon"
 	"github.com/vmware-tanzu/octant/pkg/store"
 )
 
@@ -49,12 +49,12 @@ func SetLoading(isLoading bool) Option {
 
 // Navigation is a set of navigation entries.
 type Navigation struct {
-	Module     string       `json:"module,omitempty"`
-	Title      string       `json:"title,omitempty"`
-	Path       string       `json:"path,omitempty"`
-	Children   []Navigation `json:"children,omitempty"`
-	IconName   string       `json:"iconName,omitempty"`
-	Loading    bool         `json:"isLoading"`
+	Module   string       `json:"module,omitempty"`
+	Title    string       `json:"title,omitempty"`
+	Path     string       `json:"path,omitempty"`
+	Children []Navigation `json:"children,omitempty"`
+	IconName string       `json:"iconName,omitempty"`
+	Loading  bool         `json:"isLoading"`
 }
 
 // New creates a Navigation.
@@ -72,13 +72,7 @@ func New(title, navigationPath string, options ...Option) (*Navigation, error) {
 
 // CRDEntries generates navigation entries for CRDs.
 func CRDEntries(ctx context.Context, prefix, namespace string, objectStore store.Store, wantsClusterScoped bool) ([]Navigation, bool, error) {
-	var list = []Navigation{
-		{
-			Title:   "Overview",
-			Path:    prefix,
-			Loading: false,
-		},
-	}
+	var list = []Navigation{}
 
 	loading := false
 
diff --git a/web/src/app/modules/sugarloaf/components/smart/navigation/navigation.component.html b/web/src/app/modules/sugarloaf/components/smart/navigation/navigation.component.html
index 6b72a73a..bcae382e 100644
--- a/web/src/app/modules/sugarloaf/components/smart/navigation/navigation.component.html
+++ b/web/src/app/modules/sugarloaf/components/smart/navigation/navigation.component.html
@@ -8,59 +8,45 @@
           <div class="nav-divider"></div>
         </ng-template>
 
-        <ng-container *ngIf="section.children?.length > 0; else noChildCollapsed">
-          <a clrVerticalNavLink
-             (click)="openPopup(i)"
-             [ngClass]= "{'item-selected' : i == this.lastSelection}"
-             [routerLinkActiveOptions]="{exact: false}"
-             [routerLink]="formatPath(section.path)"
-             routerLinkActive="active"
-             (mouseover)="flyoutIndex= i; lastSelection=i"
-          >
-            <clr-icon [attr.shape]="section.iconName | default:'home'" clrVerticalNavIcon></clr-icon>
-          </a>
+        <a clrVerticalNavLink
+           (click)="openPopup(i)"
+           [ngClass]= "{'item-selected' : i == this.lastSelection}"
+           [routerLinkActiveOptions]="{exact: !section.children?.length}"
+           [routerLink]="formatPath(section.path)"
+           routerLinkActive="active"
+           (mouseover)="flyoutIndex= i; lastSelection=i"
+        >
+          <clr-icon [attr.shape]="section.iconName | default:'home'" clrVerticalNavIcon></clr-icon>
+        </a>
 
-          <ng-container *ngIf="shouldExpand(i)">
-            <div class="flyout nav-group-children"
-                 (mouseleave)="flyoutIndex= -1; lastSelection= -1"
-                 (document:mouseleave)="flyoutIndex= -1; lastSelection= -1"
+        <ng-container *ngIf="shouldExpand(i)">
+          <div class="flyout nav-group-children"
+               [ngClass]= "{'flyout-empty' : !section.children?.length}"
+               (mouseleave)="flyoutIndex= -1; lastSelection= -1"
+               (document:mouseleave)="flyoutIndex= -1; lastSelection= -1"
+          >
+            <a class="flyout-header"
+               (click)="openPopup(i)"
+               [routerLinkActiveOptions]="{exact: !section.children?.length}"
+               [routerLink]="formatPath(section.path)"
+               routerLinkActive="active"
             >
-              <span class="flyout-header">{{ section.title }}</span>
-              <clr-vertical-nav-group-children class="flyout-group">
-                <ng-container *ngFor="let category of section.children; trackBy: identifyNavigationItem">
-                  <a [routerLink]="formatPath(category.path)"
-                    [routerLinkActiveOptions]="{exact: true}"
-                    routerLinkActive="active"
-                    class="flyout-item nav-link"
-                    (click)="setNavState(false, i)"
-                  >
-                  {{ category.title }}
-                  </a>
-                </ng-container>
-              </clr-vertical-nav-group-children>
-            </div>
-          </ng-container>
-        </ng-container>
-
-        <ng-template #noChildCollapsed>
-              <span class="tooltip-override tooltip tooltip-md">
-                <a clrVerticalNavLink
-                   [ngClass]= "{'item-selected' : i == this.lastSelection}"
+              {{ section.title }}
+            </a>
+            <clr-vertical-nav-group-children class="flyout-group">
+              <ng-container *ngFor="let category of section.children; trackBy: identifyNavigationItem">
+                <a [routerLink]="formatPath(category.path)"
                    [routerLinkActiveOptions]="{exact: true}"
-                   [routerLink]="formatPath(section.path)"
                    routerLinkActive="active"
-                   (mouseover)="flyoutIndex= -1; lastSelection= i"
-                   (mouseleave)="lastSelection= -1"
-                   (click)="closePopups(i)"
+                   class="flyout-item nav-link"
+                   (click)="setNavState(false, i)"
                 >
-                    <clr-icon [attr.shape]="section.iconName | default:'home'" clrVerticalNavIcon></clr-icon>
+                  {{ category.title }}
                 </a>
-                <span class="tooltip-content-override tooltip-content">
-                  <span>{{ section.title }}</span>
-                </span>
-              </span>
-        </ng-template>
-
+              </ng-container>
+            </clr-vertical-nav-group-children>
+          </div>
+        </ng-container>
       </ng-container>
       <!-- Expanded Navigation -->
       <ng-template #expanded>
@@ -75,8 +61,15 @@
             (clrVerticalNavGroupExpandedChange)="setNavState($event, i)"
           >
             <clr-icon [attr.shape]="section.iconName | default:'home'" clrVerticalNavIcon></clr-icon>
-          {{ section.title }}
+            {{ section.title }}
             <clr-vertical-nav-group-children>
+              <a clrVerticalNavLink
+                 [routerLink]="formatPath(section.path)"
+                 [routerLinkActiveOptions]="{exact: true}"
+                 routerLinkActive="active"
+              >
+                Overview
+              </a>
               <ng-container *ngFor="let category of section.children; trackBy: identifyNavigationItem">
                 <a
                   clrVerticalNavLink
@@ -85,7 +78,7 @@
                   routerLinkActive="active"
                 >
                   <clr-icon *ngIf="itemIcon(category) as categoryIcon" [attr.shape]="categoryIcon" clrVerticalNavIcon></clr-icon>
-                {{ category.title }}
+                  {{ category.title }}
                 </a>
               </ng-container>
             </clr-vertical-nav-group-children>
@@ -100,7 +93,7 @@
           >
             <clr-icon [attr.shape]="section.iconName | default:'home'" clrVerticalNavIcon></clr-icon>
             <div class="nav-item-text">
-            {{ section.title }}
+              {{ section.title }}
             </div>
           </a>
         </ng-template>
diff --git a/web/src/app/modules/sugarloaf/components/smart/navigation/navigation.component.scss b/web/src/app/modules/sugarloaf/components/smart/navigation/navigation.component.scss
index 7bd572f4..d9217dbe 100644
--- a/web/src/app/modules/sugarloaf/components/smart/navigation/navigation.component.scss
+++ b/web/src/app/modules/sugarloaf/components/smart/navigation/navigation.component.scss
@@ -4,16 +4,18 @@
 
 :host-context(body) {
   --selection-color: var(--clr-color-neutral-400);
-  --tooltip-text-color: black;
   --flyout-background-color: var(--clr-color-neutral-50);
   --flyout-color: var(--clr-global-font-color);
+  --flyout-header-color: var(--clr-vertical-nav-item-color, #666666);
+  --flyout-header-active-color: var(--clr-vertical-nav-item-active-color, #666666);
 }
 
 :host-context(body.dark) {
   --selection-color: #324f62;
-  --tooltip-text-color: #EAEDEF;
   --flyout-background-color: #1b2a32;
   --flyout-color: #acbac3;
+  --flyout-header-color: #acbac3;
+  --flyout-header-active-color: #fff;
 }
 
 .nav-group.is-expanded .nav-link .nav-text {
@@ -90,6 +92,13 @@
     padding: 3px 8px !important;
     margin-left: -1px;
   }
+  a.flyout-header {
+    color: var(--flyout-header-color);
+    text-decoration: none;
+  }
+  a.flyout-header:active {
+    color: var(--flyout-header-active-color);
+  }
 
   .nav-divider {
     border-color: #575757;
@@ -123,28 +132,12 @@
     padding: 0px;
   }
 
-  .tooltip-override {
-    position: static;
-    display: block;
-  }
+  .flyout-empty {
+    border-width: 0px;
 
-  .tooltip-content-override:before {
-    border-left-color: var(--selection-color);
-    border-top-color: var(--selection-color);
+    .flyout-header {
+      border-radius: 0px 3px 3px 0px;
+    }
   }
 
-  .tooltip-content-override {
-    color: var(--tooltip-text-color);
-    background-color: var(--selection-color);
-    z-index: 503;
-    position: fixed;
-    bottom: unset;
-    left: unset;
-    margin: unset;
-    margin-top: -72px;
-    margin-left: 52px;
-    width: auto;
-    height: auto;
-    padding: 6px 9px;
-  }
 }


Test patch:
diff --git a/pkg/navigation/navigation_test.go b/pkg/navigation/navigation_test.go
index d7d198ef..eed1cf96 100644
--- a/pkg/navigation/navigation_test.go
+++ b/pkg/navigation/navigation_test.go
@@ -101,10 +101,6 @@ func TestCRDEntries_namespace_scoped(t *testing.T) {
 	require.NoError(t, err)
 
 	namespaceExpected := []Navigation{
-		{
-			Title: "Overview",
-			Path:  "/prefix",
-		},
 		createNavForCR(t, namespaceCR.GetName()),
 	}
 
@@ -113,12 +109,7 @@ func TestCRDEntries_namespace_scoped(t *testing.T) {
 	//clusterGot, _, err := CRDEntries(ctx, "/prefix", "default", objectStore, true)
 	//require.NoError(t, err)
 	//
-	//clusterExpected := []Navigation{
-	//	{
-	//		Title: "Overview",
-	//		Path:  "/prefix",
-	//	},
-	//}
+	//clusterExpected := []Navigation{}
 
 	//assert.Equal(t, clusterExpected, clusterGot)
 }