Repository: vmware-tanzu/octant
Pull request #833 (2020-04-10 04:11:24)

Make heading anchors actually visible/usable
**What this PR does / why we need it**:

Greetings octant devs! I'm the author of `jekyll-anchor-headings` and I noticed that you're using my project in your Jekyll site; which is awesome! However, looks like there's nothing being rendered in the anchors themselves.

Like so:

![image](https://user-images.githubusercontent.com/1246453/78961352-09745480-7aa6-11ea-8108-389351142b9a.png)

I don't know *how* you wanted to design it, but here's a PR that adds some icons to the anchors. I also bumped the version of the snippet.

![image](https://user-images.githubusercontent.com/1246453/78961362-109b6280-7aa6-11ea-85c3-14ab8ad1d912.png)

**Which issue(s) this PR fixes**

None that I could find.

**Special notes for your reviewer**:

None.

**Release note**:

```
release-note
```



Patch:
diff --git a/internal/api/action_request_manager.go b/internal/api/action_request_manager.go
index 6a772bb3..d0646c68 100644
--- a/internal/api/action_request_manager.go
+++ b/internal/api/action_request_manager.go
@@ -13,7 +13,7 @@ import (
 )
 
 const (
-	RequestPerformAction = "action.octant.dev/performAction"
+	RequestPerformAction = "performAction"
 )
 
 // ActionRequestManager manages action requests. Action requests allow a generic interface
diff --git a/internal/api/context_manager.go b/internal/api/context_manager.go
index f3394bea..60a2312e 100644
--- a/internal/api/context_manager.go
+++ b/internal/api/context_manager.go
@@ -19,7 +19,7 @@ import (
 )
 
 const (
-	RequestSetContext = "action.octant.dev/setContext"
+	RequestSetContext = "setContext"
 )
 
 // ContextManagerOption is an option for configuring ContextManager.
diff --git a/internal/octant/actions.go b/internal/octant/actions.go
index 75ce56a8..4e95af2c 100644
--- a/internal/octant/actions.go
+++ b/internal/octant/actions.go
@@ -1,11 +1,5 @@
 package octant
 
 const (
-	ActionDeleteObject            = "action.octant.dev/deleteObject"
-	ActionOverviewCordon          = "action.octant.dev/cordon"
-	ActionOverviewUncordon        = "action.octant.dev/uncordon"
-	ActionOverviewContainerEditor = "action.octant.dev/containerEditor"
-	ActionOverviewCronjob         = "action.octant.dev/cronJob"
-	ActionOverviewServiceEditor   = "action.octant.dev/serviceEditor"
-	ActionDeploymentConfiguration = "action.octant.dev/deploymentConfiguration"
+	ActionDeleteObject = "octant/deleteObject"
 )
diff --git a/internal/octant/container_editor.go b/internal/octant/container_editor.go
index fdc5f22d..2e416449 100644
--- a/internal/octant/container_editor.go
+++ b/internal/octant/container_editor.go
@@ -37,7 +37,7 @@ func NewContainerEditor(objectStore store.Store) *ContainerEditor {
 
 // ActionName returns name of this action.
 func (e *ContainerEditor) ActionName() string {
-	return "action.octant.dev/containerEditor"
+	return "overview/containerEditor"
 }
 
 // Handle edits a container. Supported edits:
diff --git a/internal/octant/cordon.go b/internal/octant/cordon.go
index 69f13512..b9db6661 100644
--- a/internal/octant/cordon.go
+++ b/internal/octant/cordon.go
@@ -44,7 +44,7 @@ func NewCordon(objectStore store.Store, clusterClient cluster.ClientInterface) *
 
 // ActionName returns the name of this action
 func (c *Cordon) ActionName() string {
-	return "action.octant.dev/cordon"
+	return "overview/cordon"
 }
 
 // Handle executing cordon
@@ -147,7 +147,7 @@ func NewUncordon(objectStore store.Store, clusterClient cluster.ClientInterface)
 
 // ActionName returns the name of this action
 func (u *Uncordon) ActionName() string {
-	return "action.octant.dev/uncordon"
+	return "overview/uncordon"
 }
 
 // Handle executing uncordon
diff --git a/internal/octant/cronjob.go b/internal/octant/cronjob.go
index d8235991..a8ac10d1 100644
--- a/internal/octant/cronjob.go
+++ b/internal/octant/cronjob.go
@@ -42,7 +42,7 @@ func NewCronJobTrigger(objectStore store.Store, clusterClient cluster.ClientInte
 
 // ActionName returns the name of this action
 func (c *CronJobTrigger) ActionName() string {
-	return "action.octant.dev/cronJob"
+	return "overview/cronjob"
 }
 
 // Handle executing cronjob
diff --git a/internal/octant/deployment_configuration_editor.go b/internal/octant/deployment_configuration_editor.go
index 8d54cfd2..bf7b6ee9 100644
--- a/internal/octant/deployment_configuration_editor.go
+++ b/internal/octant/deployment_configuration_editor.go
@@ -34,7 +34,7 @@ func NewDeploymentConfigurationEditor(logger log.Logger, objectStore store.Store
 
 // ActionName returns the action name for this editor.
 func (e *DeploymentConfigurationEditor) ActionName() string {
-	return "action.octant.dev/deploymentConfiguration"
+	return "deployment/configuration"
 }
 
 // Handle edits a deployment. Supported edits:
diff --git a/internal/octant/service_configuration_editor.go b/internal/octant/service_configuration_editor.go
index a96fdb7b..906465b3 100644
--- a/internal/octant/service_configuration_editor.go
+++ b/internal/octant/service_configuration_editor.go
@@ -33,7 +33,7 @@ func NewServiceConfigurationEditor(objectStore store.Store) *ServiceConfiguratio
 
 // ActionName returns the name of this action.
 func (s *ServiceConfigurationEditor) ActionName() string {
-	return "action.octant.dev/serviceEditor"
+	return "overview/serviceEditor"
 }
 
 // Handle edits a service: Supported edits:
diff --git a/internal/printer/container.go b/internal/printer/container.go
index 92b3653f..f0eb2fce 100644
--- a/internal/printer/container.go
+++ b/internal/printer/container.go
@@ -13,8 +13,6 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/vmware-tanzu/octant/internal/octant"
-
 	"github.com/vmware-tanzu/octant/internal/portforward"
 
 	"github.com/pkg/errors"
@@ -489,7 +487,7 @@ func editContainerAction(owner runtime.Object, container *corev1.Container) (com
 		return component.Action{}, err
 	}
 
-	form, err := component.CreateFormForObject(octant.ActionOverviewContainerEditor, owner,
+	form, err := component.CreateFormForObject("overview/containerEditor", owner,
 		component.NewFormFieldText("Image", "containerImage", container.Image),
 		component.NewFormFieldHidden("containersPath", string(containersPathData)),
 		component.NewFormFieldHidden("containerName", container.Name),
diff --git a/internal/printer/cronjob.go b/internal/printer/cronjob.go
index 800fa851..a9f05f4e 100644
--- a/internal/printer/cronjob.go
+++ b/internal/printer/cronjob.go
@@ -8,7 +8,6 @@ package printer
 import (
 	"context"
 	"fmt"
-	"github.com/vmware-tanzu/octant/internal/octant"
 	"strconv"
 
 	"github.com/pkg/errors"
@@ -40,7 +39,7 @@ func CronJobListHandler(ctx context.Context, list *batchv1beta1.CronJobList, opt
 		buttonGroup := component.NewButtonGroup()
 		buttonGroup.AddButton(
 			component.NewButton("Trigger",
-				action.CreatePayload(octant.ActionOverviewCronjob, action.Payload{
+				action.CreatePayload("overview/cronjob", action.Payload{
 					"namespace":  c.Namespace,
 					"apiVersion": c.APIVersion,
 					"kind":       c.Kind,
diff --git a/internal/printer/deployment.go b/internal/printer/deployment.go
index 67879fa3..cdd226d0 100644
--- a/internal/printer/deployment.go
+++ b/internal/printer/deployment.go
@@ -8,7 +8,6 @@ package printer
 import (
 	"context"
 	"fmt"
-	"github.com/vmware-tanzu/octant/internal/octant"
 
 	"github.com/pkg/errors"
 	appsv1 "k8s.io/api/apps/v1"
@@ -262,7 +261,7 @@ func editDeploymentAction(deployment *appsv1.Deployment) ([]component.Action, er
 		return []component.Action{}, nil
 	}
 
-	form, err := component.CreateFormForObject(octant.ActionDeploymentConfiguration, deployment,
+	form, err := component.CreateFormForObject("deployment/configuration", deployment,
 		component.NewFormFieldNumber("Replicas", "replicas", fmt.Sprintf("%d", *replicas)),
 	)
 	if err != nil {
diff --git a/internal/printer/service.go b/internal/printer/service.go
index 408e0eb9..bc00fba0 100644
--- a/internal/printer/service.go
+++ b/internal/printer/service.go
@@ -8,7 +8,6 @@ package printer
 import (
 	"context"
 	"fmt"
-	"github.com/vmware-tanzu/octant/internal/octant"
 	"strings"
 
 	"github.com/pkg/errors"
@@ -238,7 +237,7 @@ func editServiceAction(ctx context.Context, service *corev1.Service, options Opt
 		}
 	}
 
-	form, err := component.CreateFormForObject(octant.ActionOverviewServiceEditor, service,
+	form, err := component.CreateFormForObject("overview/serviceEditor", service,
 		component.NewFormFieldSelect("Selectors", "selectors", choices, true))
 	if err != nil {
 		return component.Action{}, err
diff --git a/site/_includes/anchor_headings.html b/site/_includes/anchor_headings.html
index 21e2d047..4a8b32ff 100644
--- a/site/_includes/anchor_headings.html
+++ b/site/_includes/anchor_headings.html
@@ -1,6 +1,6 @@
 {% capture headingsWorkspace %}
   {% comment %}
-    Version 1.0.4
+    Version 1.0.6
       https://github.com/allejo/jekyll-anchor-headings
 
     "Be the pull request you wish to see in the world." ~Ben Balter
@@ -13,7 +13,8 @@
 
     Optional Parameters:
       * beforeHeading (bool)   : false  - Set to true if the anchor should be placed _before_ the heading's content
-      * anchorAttrs   (string) :  ''    - Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use `href`, `class` or `title`
+      * anchorAttrs   (string) :  ''    - Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use `href`, `class` or `title`;
+                                          the `%heading%` placeholder is available
       * anchorBody    (string) :  ''    - The content that will be placed inside the anchor; the `%heading%` placeholder is available
       * anchorClass   (string) :  ''    - The class(es) that will be used for each anchor. Separate multiple classes with a space
       * anchorTitle   (string) :  ''    - The `title` attribute that will be used for anchors
@@ -43,11 +44,15 @@
     {% assign nextChar = node | replace: '"', '' | strip | slice: 0, 1 %}
     {% assign headerLevel = nextChar | times: 1 %}
 
-    <!-- If the level is cast to 0, it means it's not a h1-h6 tag, so let's try to fix it -->
+    <!-- If the level is cast to 0, it means it's not a h1-h6 tag, so let's see if we need to fix it -->
     {% if headerLevel == 0 %}
-      {% if nextChar != '<' and nextChar != '' %}
+      <!-- Split up the node based on closing angle brackets and get the first one. -->
+      {% assign firstChunk = node | split: '>' | first %}
+
+      <!-- If the first chunk does NOT contain a '<', that means we've broken another HTML tag that starts with 'h' -->
+      {% unless firstChunk contains '<' %}
         {% capture node %}<h{{ node }}{% endcapture %}
-      {% endif %}
+      {% endunless %}
 
       {% capture edited_headings %}{{ edited_headings }}{{ node }}{% endcapture %}
       {% continue %}
@@ -76,7 +81,7 @@
       {% endif %}
 
       {% if include.anchorAttrs %}
-        {% capture anchor %}{{ anchor }} {{ include.anchorAttrs }}{% endcapture %}
+        {% capture anchor %}{{ anchor }} {{ include.anchorAttrs | replace: '%heading%', header }}{% endcapture %}
       {% endif %}
 
       {% capture anchor %}<a {{ anchor }}>{{ include.anchorBody | replace: '%heading%', header | default: '' }}</a>{% endcapture %}
@@ -102,4 +107,4 @@
     {% endcapture %}
     {% capture edited_headings %}{{ edited_headings }}{{ new_heading }}{% endcapture %}
   {% endfor %}
-{% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }}
\ No newline at end of file
+{% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }}
diff --git a/site/_layouts/docs.html b/site/_layouts/docs.html
index f22f5401..e8461aed 100644
--- a/site/_layouts/docs.html
+++ b/site/_layouts/docs.html
@@ -31,7 +31,12 @@
               </div>
               <div class="col-md-8">
               <div class="documentation-container">
-                {% include anchor_headings.html html=content %}
+                {% include anchor_headings.html
+                     html=content
+                     anchorClass="anchor-link"
+                     anchorBody='<i class="fas fa-link"></i><noscript>#</noscript>'
+                     anchorAttrs='aria-label="Link to &quot;%heading%&quot;"'
+                %}
               </div>
               </div>
             </div>
diff --git a/site/_scss/site/common/_type.scss b/site/_scss/site/common/_type.scss
index f5dfaacf..55533666 100644
--- a/site/_scss/site/common/_type.scss
+++ b/site/_scss/site/common/_type.scss
@@ -6,6 +6,11 @@ h4,
 h5,
 h6 {
   font-weight: $font-weight-medium;
+
+  a.anchor-link {
+    font-size: 0.55em;
+    vertical-align: middle;
+  }
 }
 
 h1,
diff --git a/web/src/app/modules/shared/terminals/terminals.service.ts b/web/src/app/modules/shared/terminals/terminals.service.ts
index 171f1b1a..775bff38 100644
--- a/web/src/app/modules/shared/terminals/terminals.service.ts
+++ b/web/src/app/modules/shared/terminals/terminals.service.ts
@@ -33,7 +33,7 @@ export class TerminalOutputStreamer {
 
   private terminalUrl(): string {
     return [
-      'event.octant.dev',
+      'event.octant.dev/',
       'terminals',
       `namespace/${this.namespace}`,
       `pod/${this.pod}`,


Test patch:
diff --git a/internal/octant/container_editor_test.go b/internal/octant/container_editor_test.go
index 91aa904b..98f18b14 100644
--- a/internal/octant/container_editor_test.go
+++ b/internal/octant/container_editor_test.go
@@ -53,7 +53,7 @@ func TestNewContainerEditor(t *testing.T) {
 
 	ctx := context.Background()
 
-	payload := action.CreatePayload(ActionOverviewContainerEditor, map[string]interface{}{
+	payload := action.CreatePayload("overview/containerEditor", map[string]interface{}{
 		"apiVersion":     "apps/v1",
 		"kind":           "Deployment",
 		"namespace":      "default",
diff --git a/internal/octant/cordon_test.go b/internal/octant/cordon_test.go
index 25e928fa..a8788fb1 100644
--- a/internal/octant/cordon_test.go
+++ b/internal/octant/cordon_test.go
@@ -95,9 +95,9 @@ func Test_Cordon(t *testing.T) {
 			require.NoError(t, err)
 
 			cordon := octant.NewCordon(objectStore, clusterClient)
-			assert.Equal(t, octant.ActionOverviewCordon, cordon.ActionName())
+			assert.Equal(t, "overview/cordon", cordon.ActionName())
 
-			payload := action.CreatePayload(octant.ActionOverviewCordon, map[string]interface{}{
+			payload := action.CreatePayload("overview/cordon", map[string]interface{}{
 				"apiVersion": "v1",
 				"kind":       "Node",
 				"name":       tc.key.Name,
@@ -178,16 +178,16 @@ func Test_Uncordon(t *testing.T) {
 			_, err := fakeClientset.CoreV1().Nodes().Create(node)
 			require.NoError(t, err)
 
-			uncordon := octant.NewUncordon(objectStore, clusterClient)
-			assert.Equal(t, octant.ActionOverviewUncordon, uncordon.ActionName())
+			cordon := octant.NewUncordon(objectStore, clusterClient)
+			assert.Equal(t, "overview/uncordon", cordon.ActionName())
 
-			payload := action.CreatePayload(octant.ActionOverviewUncordon, map[string]interface{}{
+			payload := action.CreatePayload("overview/uncordon", map[string]interface{}{
 				"apiVersion": "v1",
 				"kind":       "Node",
 				"name":       tc.key.Name,
 			})
 
-			require.NoError(t, uncordon.Handle(ctx, alerter, payload))
+			require.NoError(t, cordon.Handle(ctx, alerter, payload))
 		})
 	}
 }
diff --git a/internal/octant/cronjob_test.go b/internal/octant/cronjob_test.go
index 7d5f823b..fc418072 100644
--- a/internal/octant/cronjob_test.go
+++ b/internal/octant/cronjob_test.go
@@ -76,9 +76,9 @@ func Test_CronJobTrigger(t *testing.T) {
 	require.NoError(t, err)
 
 	trigger := octant.NewCronJobTrigger(objectStore, clusterClient)
-	assert.Equal(t, octant.ActionOverviewCronjob, trigger.ActionName())
+	assert.Equal(t, "overview/cronjob", trigger.ActionName())
 
-	payload := action.CreatePayload(octant.ActionOverviewCronjob, map[string]interface{}{
+	payload := action.CreatePayload("overview/cronjob", map[string]interface{}{
 		"namespace":  key.Namespace,
 		"apiVersion": key.APIVersion,
 		"kind":       key.Kind,
diff --git a/internal/octant/deployment_configuration_editor_test.go b/internal/octant/deployment_configuration_editor_test.go
index 334cd717..cf60676b 100644
--- a/internal/octant/deployment_configuration_editor_test.go
+++ b/internal/octant/deployment_configuration_editor_test.go
@@ -56,7 +56,7 @@ func TestDeploymentConfigurationEditor(t *testing.T) {
 		})
 
 	configurationEditor := NewDeploymentConfigurationEditor(logger, objectStore)
-	assert.Equal(t, ActionDeploymentConfiguration, configurationEditor.ActionName())
+	assert.Equal(t, "deployment/configuration", configurationEditor.ActionName())
 
 	ctx := context.Background()
 
diff --git a/internal/octant/service_configuration_editor_test.go b/internal/octant/service_configuration_editor_test.go
index 147bade7..5eb88473 100644
--- a/internal/octant/service_configuration_editor_test.go
+++ b/internal/octant/service_configuration_editor_test.go
@@ -54,7 +54,7 @@ func TestServiceConfigurationEditor(t *testing.T) {
 		})
 
 	configurationEditor := NewServiceConfigurationEditor(objectStore)
-	assert.Equal(t, ActionOverviewServiceEditor, configurationEditor.ActionName())
+	assert.Equal(t, "overview/serviceEditor", configurationEditor.ActionName())
 
 	ctx := context.Background()
 
diff --git a/internal/printer/container_test.go b/internal/printer/container_test.go
index d7927d91..d45e1391 100644
--- a/internal/printer/container_test.go
+++ b/internal/printer/container_test.go
@@ -8,7 +8,6 @@ package printer
 import (
 	"context"
 	"fmt"
-	"github.com/vmware-tanzu/octant/internal/octant"
 	"reflect"
 	"testing"
 	"time"
@@ -472,7 +471,7 @@ func Test_editContainerAction(t *testing.T) {
 	got, err := editContainerAction(deployment, &container)
 	require.NoError(t, err)
 
-	form, err := component.CreateFormForObject(octant.ActionOverviewContainerEditor, deployment,
+	form, err := component.CreateFormForObject("overview/containerEditor", deployment,
 		component.NewFormFieldText("Image", "containerImage", container.Image),
 		component.NewFormFieldHidden("containersPath", `["spec","template","spec","containers"]`),
 		component.NewFormFieldHidden("containerName", container.Name),
diff --git a/internal/printer/cronjob_test.go b/internal/printer/cronjob_test.go
index a383d28b..c8b3ed43 100644
--- a/internal/printer/cronjob_test.go
+++ b/internal/printer/cronjob_test.go
@@ -7,7 +7,6 @@ package printer
 
 import (
 	"context"
-	"github.com/vmware-tanzu/octant/internal/octant"
 	"testing"
 	"time"
 
@@ -71,7 +70,7 @@ func Test_CronJobListHandler(t *testing.T) {
 	buttonGroup := component.NewButtonGroup()
 	buttonGroup.AddButton(
 		component.NewButton("Trigger",
-			action.CreatePayload(octant.ActionOverviewCronjob, action.Payload{
+			action.CreatePayload("overview/cronjob", action.Payload{
 				"namespace":  cronJob.Namespace,
 				"apiVersion": cronJob.APIVersion,
 				"kind":       cronJob.Kind,
diff --git a/internal/printer/deployment_test.go b/internal/printer/deployment_test.go
index 31bc4ece..6d16bdec 100644
--- a/internal/printer/deployment_test.go
+++ b/internal/printer/deployment_test.go
@@ -7,7 +7,6 @@ package printer
 
 import (
 	"context"
-	"github.com/vmware-tanzu/octant/internal/octant"
 	"testing"
 	"time"
 
@@ -372,7 +371,7 @@ func Test_editDeploymentAction(t *testing.T) {
 				component.NewFormFieldHidden("kind", kind),
 				component.NewFormFieldHidden("name", deployment.Name),
 				component.NewFormFieldHidden("namespace", deployment.Namespace),
-				component.NewFormFieldHidden("action", octant.ActionDeploymentConfiguration),
+				component.NewFormFieldHidden("action", "deployment/configuration"),
 			},
 		},
 	}