Repository: vmware-tanzu/octant
Pull request #542 (2020-01-16 15:32:24)

Move object metadata to its own tab
Also:

* replaced github.com/pkg/errors with fmt.Errorf

<img width="1629" alt="Screen Shot 2020-01-16 at 10 20 54 AM" src="https://user-images.githubusercontent.com/240/72538185-6a93df80-384b-11ea-81ac-584a7114eeea.png">

<img width="1629" alt="Screen Shot 2020-01-16 at 10 20 58 AM" src="https://user-images.githubusercontent.com/240/72538163-64056800-384b-11ea-8b2e-e8ea3f5bd8a6.png">




Signed-off-by: bryanl <bryanliles@gmail.com>

wwitzel3: fixes #539 
wwitzel3: Can you add a changelog entry for this one.

Patch:
diff --git a/changelogs/unreleased/539-bryanl b/changelogs/unreleased/539-bryanl
new file mode 100644
index 00000000..cda0439d
--- /dev/null
+++ b/changelogs/unreleased/539-bryanl
@@ -0,0 +1,1 @@
+Move object metadata to its own tab
diff --git a/internal/describer/crd.go b/internal/describer/crd.go
index 09f4b7c0..fb152105 100644
--- a/internal/describer/crd.go
+++ b/internal/describer/crd.go
@@ -27,6 +27,7 @@ import (
 )
 
 type crdPrinter func(ctx context.Context, crd, object *unstructured.Unstructured, options printer.Options) (component.Component, error)
+type metadataPrinter func(runtime.Object, link.Interface) (*component.FlexLayout, error)
 type resourceViewerPrinter func(ctx context.Context, object *unstructured.Unstructured, dashConfig config.Dash, q queryer.Queryer) (component.Component, error)
 type yamlPrinter func(runtime.Object) (*component.YAML, error)
 
@@ -38,6 +39,7 @@ type crd struct {
 	path                  string
 	name                  string
 	summaryPrinter        crdPrinter
+	metadataPrinter       metadataPrinter
 	resourceViewerPrinter resourceViewerPrinter
 	yamlPrinter           yamlPrinter
 }
@@ -49,6 +51,7 @@ func newCRD(name, path string, options ...crdOption) *crd {
 		path:                  path,
 		name:                  name,
 		summaryPrinter:        printer.CustomResourceHandler,
+		metadataPrinter:       printer.MetadataHandler,
 		resourceViewerPrinter: createCRDResourceViewer,
 		yamlPrinter:           yamlviewer.ToComponent,
 	}
@@ -130,6 +133,13 @@ func (c *crd) Describe(ctx context.Context, namespace string, options Options) (
 
 	cr.Add(summary)
 
+	metadata, err := c.metadataPrinter(object, linkGenerator)
+	if err != nil {
+		return component.EmptyContentResponse, err
+	}
+	metadata.SetAccessor("metadata")
+	cr.Add(metadata)
+
 	resourceViewerComponent, err := c.resourceViewerPrinter(ctx, object, options, options.Queryer)
 	if err != nil {
 		return component.EmptyContentResponse, err
diff --git a/internal/describer/crd_list.go b/internal/describer/crd_list.go
index e597df71..14a33b6a 100644
--- a/internal/describer/crd_list.go
+++ b/internal/describer/crd_list.go
@@ -70,6 +70,13 @@ func (cld *crdList) Describe(ctx context.Context, namespace string, options Opti
 	contentResponse := component.NewContentResponse(title)
 	contentResponse.Add(view)
 
+	metadata, err := printer.MetadataHandler(crd, options.Link)
+	if err != nil {
+		return component.EmptyContentResponse, err
+	}
+	metadata.SetAccessor("metadata")
+	contentResponse.Add(metadata)
+
 	yamlView, err := yamlviewer.ToComponent(crd)
 	if err != nil {
 		return component.EmptyContentResponse, err
diff --git a/internal/describer/object.go b/internal/describer/object.go
index b017bef0..f231533d 100644
--- a/internal/describer/object.go
+++ b/internal/describer/object.go
@@ -7,8 +7,8 @@ package describer
 
 import (
 	"context"
+	"fmt"
 
-	"github.com/pkg/errors"
 	"k8s.io/apimachinery/pkg/api/meta"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes/scheme"
@@ -17,6 +17,7 @@ import (
 	"github.com/vmware-tanzu/octant/internal/log"
 	"github.com/vmware-tanzu/octant/internal/modules/overview/logviewer"
 	"github.com/vmware-tanzu/octant/internal/modules/overview/yamlviewer"
+	"github.com/vmware-tanzu/octant/internal/printer"
 	"github.com/vmware-tanzu/octant/internal/resourceviewer"
 	"github.com/vmware-tanzu/octant/pkg/store"
 	"github.com/vmware-tanzu/octant/pkg/view/component"
@@ -61,6 +62,7 @@ func NewObject(c ObjectConfig) *Object {
 
 	o.tabFuncDescriptors = []tabFuncDescriptor{
 		{name: "summary", tabFunc: o.addSummaryTab},
+		{name: "metadata", tabFunc: o.addMetadataTab},
 		{name: "resource viewer", tabFunc: o.addResourceViewerTab},
 		{name: "yaml", tabFunc: o.addYAMLViewerTab},
 		{name: "logs", tabFunc: o.addLogsTab},
@@ -84,17 +86,17 @@ func (d *Object) Describe(ctx context.Context, namespace string, options Options
 	if err != nil {
 		return component.EmptyContentResponse, api.NewNotFoundError(d.path)
 	} else if object == nil {
-		return component.EmptyContentResponse, errors.Errorf("unable to load object %s", d.objectStoreKey)
+		return component.EmptyContentResponse, fmt.Errorf("unable to load object %s", d.objectStoreKey)
 	}
 
 	item := d.objectType()
 
 	if err := scheme.Scheme.Convert(object, item, nil); err != nil {
-		return component.EmptyContentResponse, errors.Wrapf(err, "converting dynamic object to a type")
+		return component.EmptyContentResponse, fmt.Errorf("converting dynamic object to a type: %w", err)
 	}
 
 	if err := copyObjectMeta(item, object); err != nil {
-		return component.EmptyContentResponse, errors.Wrap(err, "copying object metadata")
+		return component.EmptyContentResponse, fmt.Errorf("copying object metadata: %w", err)
 	}
 
 	accessor := meta.NewAccessor()
@@ -111,7 +113,7 @@ func (d *Object) Describe(ctx context.Context, namespace string, options Options
 
 	currentObject, ok := item.(runtime.Object)
 	if !ok {
-		return component.EmptyContentResponse, errors.Errorf("expected item to be a runtime object. It was a %T",
+		return component.EmptyContentResponse, fmt.Errorf("expected item to be a runtime object. It was a %T",
 			item)
 	}
 
@@ -133,7 +135,7 @@ func (d *Object) Describe(ctx context.Context, namespace string, options Options
 
 	tabs, err := options.PluginManager().Tabs(ctx, object)
 	if err != nil {
-		return component.EmptyContentResponse, errors.Wrap(err, "getting tabs from plugins")
+		return component.EmptyContentResponse, fmt.Errorf("getting tabs from plugins: %w", err)
 	}
 
 	for _, tab := range tabs {
@@ -153,7 +155,7 @@ func (d *Object) PathFilters() []PathFilter {
 func (d *Object) addSummaryTab(ctx context.Context, object runtime.Object, cr *component.ContentResponse, options Options) error {
 	vc, err := options.Printer.Print(ctx, object, options.PluginManager())
 	if vc == nil {
-		return errors.Wrap(err, "unable to print a nil object")
+		return fmt.Errorf("unable to print a nil object: %w", err)
 	}
 
 	if err != nil {
@@ -201,7 +203,26 @@ func (d *Object) addResourceViewerTab(ctx context.Context, object runtime.Object
 	return nil
 }
 
-func (d *Object) addYAMLViewerTab(ctx context.Context, object runtime.Object, cr *component.ContentResponse, options Options) error {
+func (d *Object) addMetadataTab(ctx context.Context, object runtime.Object, cr *component.ContentResponse, options Options) error {
+	logger := log.From(ctx)
+
+	metadataComponent, err := printer.MetadataHandler(object, options.Link)
+	if err != nil {
+		errComponent := component.NewError(component.TitleFromString("Metadata"), err)
+		cr.Add(errComponent)
+
+		logger.WithErr(err).Errorf("create metadata component")
+
+		return nil
+	}
+
+	metadataComponent.SetAccessor("metadata")
+	cr.Add(metadataComponent)
+
+	return nil
+}
+
+func (d *Object) addYAMLViewerTab(ctx context.Context, object runtime.Object, cr *component.ContentResponse, _ Options) error {
 	yvComponent, err := yamlviewer.ToComponent(object)
 
 	if err != nil {
@@ -217,10 +238,9 @@ func (d *Object) addYAMLViewerTab(ctx context.Context, object runtime.Object, cr
 	yvComponent.SetAccessor("yaml")
 	cr.Add(yvComponent)
 	return nil
-
 }
 
-func (d *Object) addLogsTab(ctx context.Context, object runtime.Object, cr *component.ContentResponse, options Options) error {
+func (d *Object) addLogsTab(ctx context.Context, object runtime.Object, cr *component.ContentResponse, _ Options) error {
 	if isPod(object) {
 		logsComponent, err := logviewer.ToComponent(object)
 		if err != nil {
diff --git a/internal/printer/metadata.go b/internal/printer/metadata.go
index 007adb7d..10e3de63 100644
--- a/internal/printer/metadata.go
+++ b/internal/printer/metadata.go
@@ -6,27 +6,54 @@ SPDX-License-Identifier: Apache-2.0
 package printer
 
 import (
-	"github.com/pkg/errors"
+	"fmt"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 
-	link2 "github.com/vmware-tanzu/octant/internal/link"
+	"github.com/vmware-tanzu/octant/internal/link"
 	"github.com/vmware-tanzu/octant/pkg/view/component"
 	"github.com/vmware-tanzu/octant/pkg/view/flexlayout"
 )
 
+//  MetadataHandler converts object metadata to a flex layout containing object metadata.
+func MetadataHandler(object runtime.Object, linkGenerator link.Interface) (*component.FlexLayout, error) {
+	if object == nil {
+		return nil, fmt.Errorf("can't create metadata view for nil object")
+	}
+
+	if linkGenerator == nil {
+		return nil, fmt.Errorf("link generator is required")
+	}
+
+	layout := flexlayout.New()
+
+	metadata, err := NewMetadata(object, linkGenerator)
+	if err != nil {
+		return nil, fmt.Errorf("create metadata generator: %v", err)
+	}
+
+	if err := metadata.AddToFlexLayout(layout); err != nil {
+		return nil, fmt.Errorf("add metadata to layout: %w", err)
+	}
+
+	return layout.ToComponent("Metadata"), nil
+}
+
+// Metadata represents object metadata.
 type Metadata struct {
 	object runtime.Object
-	link   link2.Interface
+	link   link.Interface
 }
 
-func NewMetadata(object runtime.Object, l link2.Interface) (*Metadata, error) {
+// NewMetadata creates an instance of Metadata.
+func NewMetadata(object runtime.Object, l link.Interface) (*Metadata, error) {
 	if object == nil {
-		return nil, errors.New("object is nil")
+		return nil, fmt.Errorf("object is nil")
 	}
 
 	if l == nil {
-		return nil, errors.New("link generator is nil")
+		return nil, fmt.Errorf("link generator is nil")
 	}
 
 	return &Metadata{
@@ -35,20 +62,21 @@ func NewMetadata(object runtime.Object, l link2.Interface) (*Metadata, error) {
 	}, nil
 }
 
+// AddToFlexLayout adds metadata to a flex layout.
 func (m *Metadata) AddToFlexLayout(fl *flexlayout.FlexLayout) error {
 	if fl == nil {
-		return errors.New("flex layout is nil")
+		return fmt.Errorf("flex layout is nil")
 	}
 
 	section := fl.AddSection()
 
 	summary, err := m.createSummary()
 	if err != nil {
-		return errors.Wrap(err, "create summary")
+		return fmt.Errorf("create summary: %w", err)
 	}
 
 	if err := section.Add(summary, component.WidthFull); err != nil {
-		return errors.Wrap(err, "add summary to layout")
+		return fmt.Errorf("add summary to layout: %w", err)
 	}
 
 	return nil
@@ -59,7 +87,7 @@ func (m *Metadata) createSummary() (*component.Summary, error) {
 
 	object, ok := m.object.(metav1.Object)
 	if !ok {
-		return nil, errors.New("object is a meta v1 object")
+		return nil, fmt.Errorf("object is a meta v1 object")
 	}
 
 	sections.Add("Age", component.NewTimestamp(object.GetCreationTimestamp().Time))
diff --git a/internal/printer/object.go b/internal/printer/object.go
index 24300bd8..9b3cd4b4 100644
--- a/internal/printer/object.go
+++ b/internal/printer/object.go
@@ -9,7 +9,6 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/pkg/errors"
 	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/meta"
@@ -29,23 +28,10 @@ type ObjectInterface interface {
 	AddButton(name string, payload action.Payload, buttonOptions ...component.ButtonOption)
 }
 
-func defaultMetadataGen(object runtime.Object, fl *flexlayout.FlexLayout, options Options) error {
-	metadata, err := NewMetadata(object, options.Link)
-	if err != nil {
-		return errors.Wrap(err, "create metadata generator")
-	}
-
-	if err := metadata.AddToFlexLayout(fl); err != nil {
-		return errors.Wrap(err, "add metadata to layout")
-	}
-
-	return nil
-}
-
 func defaultPodTemplateGen(ctx context.Context, object runtime.Object, template corev1.PodTemplateSpec, fl *flexlayout.FlexLayout, options Options) error {
 	podTemplate := NewPodTemplate(object, template)
 	if err := podTemplate.AddToFlexLayout(ctx, fl, options); err != nil {
-		return errors.Wrap(err, "add pod template to layout")
+		return fmt.Errorf("add pod template to layout: %w", err)
 	}
 
 	return nil
@@ -54,7 +40,7 @@ func defaultPodTemplateGen(ctx context.Context, object runtime.Object, template
 func defaultJobTemplateGen(ctx context.Context, object runtime.Object, template batchv1beta1.JobTemplateSpec, fl *flexlayout.FlexLayout, options Options) error {
 	podTemplate := NewJobTemplate(ctx, object, template)
 	if err := podTemplate.AddToFlexLayout(fl, options); err != nil {
-		return errors.Wrap(err, "add job template to layout")
+		return fmt.Errorf("add job template to layout: %w", err)
 	}
 
 	return nil
@@ -62,7 +48,7 @@ func defaultJobTemplateGen(ctx context.Context, object runtime.Object, template
 
 func defaultEventsGen(ctx context.Context, object runtime.Object, fl *flexlayout.FlexLayout, options Options) error {
 	if err := createEventsForObject(ctx, fl, object, options); err != nil {
-		return errors.Wrap(err, "add events to layout")
+		return fmt.Errorf("add events to layout: %w", err)
 	}
 
 	return nil
@@ -109,7 +95,6 @@ type Object struct {
 
 	flexLayout *flexlayout.FlexLayout
 
-	MetadataGen    func(runtime.Object, *flexlayout.FlexLayout, Options) error
 	PodTemplateGen func(context.Context, runtime.Object, corev1.PodTemplateSpec, *flexlayout.FlexLayout, Options) error
 	JobTemplateGen func(context.Context, runtime.Object, batchv1beta1.JobTemplateSpec, *flexlayout.FlexLayout, Options) error
 	EventsGen      func(ctx context.Context, object runtime.Object, fl *flexlayout.FlexLayout, options Options) error
@@ -121,7 +106,6 @@ func NewObject(object runtime.Object, options ...ObjectOpts) *Object {
 		object:     object,
 		flexLayout: flexlayout.New(),
 
-		MetadataGen:    defaultMetadataGen,
 		PodTemplateGen: defaultPodTemplateGen,
 		JobTemplateGen: defaultJobTemplateGen,
 		EventsGen:      defaultEventsGen,
@@ -169,7 +153,7 @@ func (o *Object) RegisterItems(items ...ItemDescriptor) {
 
 func (o *Object) summaryComponent(title string, summary *component.Summary, section *flexlayout.Section, additional ...component.SummarySection) error {
 	if section == nil {
-		return errors.Errorf("section is nil")
+		return fmt.Errorf("section is nil")
 	}
 
 	if summary == nil {
@@ -185,7 +169,7 @@ func (o *Object) summaryComponent(title string, summary *component.Summary, sect
 	}
 
 	if err := section.Add(summary, component.WidthHalf); err != nil {
-		return errors.Wrapf(err, "add component to %q layout", title)
+		return fmt.Errorf("add component to %q layout: %w", title, err)
 	}
 
 	return nil
@@ -193,7 +177,7 @@ func (o *Object) summaryComponent(title string, summary *component.Summary, sect
 
 func deleteObjectConfirmation(object runtime.Object) (component.ButtonOption, error) {
 	if object == nil {
-		return nil, errors.New("object is nil")
+		return nil, fmt.Errorf("object is nil")
 	}
 	_, kind := object.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind()
 
@@ -210,7 +194,7 @@ func deleteObjectConfirmation(object runtime.Object) (component.ButtonOption, er
 // ToComponent converts Object to a view.
 func (o *Object) ToComponent(ctx context.Context, options Options) (component.Component, error) {
 	if o.object == nil {
-		return nil, errors.New("object is nil")
+		return nil, fmt.Errorf("object is nil")
 	}
 
 	accessor, err := meta.Accessor(o.object)
@@ -226,7 +210,7 @@ func (o *Object) ToComponent(ctx context.Context, options Options) (component.Co
 
 		confirmation, err := deleteObjectConfirmation(o.object)
 		if err != nil {
-			return nil, errors.Wrap(err, "create delete confirmation")
+			return nil, fmt.Errorf("create delete confirmation: %w", err)
 		}
 
 		o.AddButton("Delete", action.CreatePayload(octant.ActionDeleteObject,
@@ -237,24 +221,20 @@ func (o *Object) ToComponent(ctx context.Context, options Options) (component.Co
 
 	pluginPrinter := options.DashConfig.PluginManager()
 	if pluginPrinter == nil {
-		return nil, errors.New("plugin printer is nil")
+		return nil, fmt.Errorf("plugin printer is nil")
 	}
 
 	pr, err := pluginPrinter.Print(ctx, o.object)
 	if err != nil {
-		return nil, errors.Wrap(err, "plugin manager")
+		return nil, fmt.Errorf("plugin manager: %w", err)
 	}
 
 	if err := o.summaryComponent("Configuration", o.config, summarySection, pr.Config...); err != nil {
-		return nil, errors.Wrap(err, "generate configuration component")
+		return nil, fmt.Errorf("generate configuration component: %w", err)
 	}
 
 	if err := o.summaryComponent("Status", o.summary, summarySection, pr.Status...); err != nil {
-		return nil, errors.Wrap(err, "generate summary component")
-	}
-
-	if err := o.MetadataGen(o.object, o.flexLayout, options); err != nil {
-		return nil, errors.Wrap(err, "generate metadata")
+		return nil, fmt.Errorf("generate summary component: %w", err)
 	}
 
 	for _, items := range o.itemsLists {
@@ -263,11 +243,11 @@ func (o *Object) ToComponent(ctx context.Context, options Options) (component.Co
 		for _, item := range items {
 			vc, err := item.Func()
 			if err != nil {
-				return nil, errors.Wrap(err, "failed to create item view")
+				return nil, fmt.Errorf("failed to create item view: %w", err)
 			}
 
 			if err := section.Add(vc, item.Width); err != nil {
-				return nil, errors.Wrap(err, "unable to add item to layout section in object printer")
+				return nil, fmt.Errorf("unable to add item to layout section in object printer: %w", err)
 			}
 		}
 	}
@@ -277,26 +257,26 @@ func (o *Object) ToComponent(ctx context.Context, options Options) (component.Co
 
 		for _, item := range pr.Items {
 			if err := section.Add(item.View, item.Width); err != nil {
-				return nil, errors.Wrap(err, "unable to add plugin item to layout section in object printer")
+				return nil, fmt.Errorf("unable to add plugin item to layout section in object printer: %w", err)
 			}
 		}
 	}
 
 	if o.isPodTemplateEnabled {
 		if err := o.PodTemplateGen(ctx, o.object, o.podTemplateOptions.template, o.flexLayout, options); err != nil {
-			return nil, errors.Wrap(err, "generate pod template")
+			return nil, fmt.Errorf("generate pod template: %w", err)
 		}
 	}
 
 	if o.isJobTemplateEnabled {
 		if err := o.JobTemplateGen(ctx, o.object, o.jobTemplateOptions.template, o.flexLayout, options); err != nil {
-			return nil, errors.Wrap(err, "generate job template")
+			return nil, fmt.Errorf("generate job template: %w", err)
 		}
 	}
 
 	if o.isEventsEnabled {
 		if err := o.EventsGen(ctx, o.object, o.flexLayout, options); err != nil {
-			return nil, errors.Wrap(err, "add events to layout")
+			return nil, fmt.Errorf("add events to layout: %w", err)
 		}
 	}
 
diff --git a/web/cypress/integration/plugin.spec.js b/web/cypress/integration/plugin.spec.js
index d378c53b..5d794138 100644
--- a/web/cypress/integration/plugin.spec.js
+++ b/web/cypress/integration/plugin.spec.js
@@ -20,7 +20,7 @@ describe('Plugin', () => {
 
       cy.get('[class=nav]')
         .find('button')
-        .should('have.length', 5)
+        .should('have.length', 6)
         .last()
         .contains('Extra Pod Details')
         .click();


Test patch:
diff --git a/internal/printer/object_test.go b/internal/printer/object_test.go
index 27f1ffde..fe608e26 100644
--- a/internal/printer/object_test.go
+++ b/internal/printer/object_test.go
@@ -44,21 +44,6 @@ func Test_Object_ToComponent(t *testing.T) {
 		},
 	}
 
-	metadataSection := component.FlexLayoutSection{
-		{
-			Width: component.WidthHalf,
-			View:  component.NewText("metadata"),
-		},
-	}
-
-	fnMetadata := func(o *Object) {
-		o.MetadataGen = func(object runtime.Object, fl *flexlayout.FlexLayout, options Options) error {
-			section := fl.AddSection()
-			require.NoError(t, section.Add(component.NewText("metadata"), 12))
-			return nil
-		}
-	}
-
 	fnPodTemplate := func(o *Object) {
 		o.PodTemplateGen = func(_ context.Context, _ runtime.Object, _ corev1.PodTemplateSpec, fl *flexlayout.FlexLayout, options Options) error {
 			section := fl.AddSection()
@@ -97,7 +82,6 @@ func Test_Object_ToComponent(t *testing.T) {
 			},
 			sections: []component.FlexLayoutSection{
 				defaultConfigSection,
-				metadataSection,
 			},
 		},
 		{
@@ -124,7 +108,6 @@ func Test_Object_ToComponent(t *testing.T) {
 							}...),
 					},
 				},
-				metadataSection,
 			},
 		},
 		{
@@ -136,7 +119,6 @@ func Test_Object_ToComponent(t *testing.T) {
 			},
 			sections: []component.FlexLayoutSection{
 				defaultConfigSection,
-				metadataSection,
 				{
 					{
 						Width: component.WidthHalf,
@@ -154,7 +136,6 @@ func Test_Object_ToComponent(t *testing.T) {
 			},
 			sections: []component.FlexLayoutSection{
 				defaultConfigSection,
-				metadataSection,
 				{
 					{
 						Width: component.WidthHalf,
@@ -175,7 +156,6 @@ func Test_Object_ToComponent(t *testing.T) {
 			},
 			sections: []component.FlexLayoutSection{
 				defaultConfigSection,
-				metadataSection,
 			},
 		},
 		{
@@ -206,7 +186,6 @@ func Test_Object_ToComponent(t *testing.T) {
 			},
 			sections: []component.FlexLayoutSection{
 				defaultConfigSection,
-				metadataSection,
 				{
 					{
 						Width: component.WidthHalf,
@@ -240,7 +219,7 @@ func Test_Object_ToComponent(t *testing.T) {
 			tpo := newTestPrinterOptions(controller)
 			printOptions := tpo.ToOptions()
 
-			o := NewObject(tc.object, fnMetadata, fnPodTemplate, fnEvent)
+			o := NewObject(tc.object, fnPodTemplate, fnEvent)
 
 			o.RegisterConfig(defaultConfig)