Skip to main content

Overview

Witboost Custom View is a feature that allows platform users to customize some pages of the application to accommodate customers' specific needs. Currently, this approach is applied to overview pages in the marketplace and the builder.

Concepts

The basic idea is that we divide the data processing code, which produces a single object structure with all the data, from the presentation layer. The link between the two is implemented by path references. This will allow the platform team to define pages that show only a subset of the data, or that show the data in different ways. Let's see some examples:

  • after creating a complex data structure, with a lot of fields, we want the user to see only a small subset of them, like just the name, and the description;
  • we want to show the same data in different ways, like a table, a list, or a sequence of cards;
  • after deployment, the descriptor is enriched with some fields that we don't want to show to the end user, so we want to remove them from the page;
  • we want to show some data only if a certain condition is met, like showing a field only if it is not empty.

The Custom View feature allows us to safely serialize and deserialize the presentation layer in a file that can be stored outside the code in a configuration backend. The serialization format of choice is YAML, and the current backend implementation supports YAML presentation definitions stored in Witboost's database. This YAML representation of the page is a structure that maps fields of the object to present to components exposed by Witboost. Witboost provides a catalog of these components out-of-the-box: a set of components implementing generic functions, like showing a string, and others that implement specific functions of the program in an opaque way, like the marketplace Q&A widget.

Selecting the right Custom View

Each page for which Custom Views are available is identified by a single page ID (like marketplace_system for the system details page in the marketplace). If you want to define a new page for all the entities, you can simply add a new custom view entry for that page ID, without specifying any other fields.

note

When there isn't any custom definition, the page will show the default defined in the code.

The list of pages that can be customized is:

Page IDPage DescriptionInput Object
marketplace_subcomponent_drawerThe drawer that shows the sub-component details of a component in the marketplace.Marketplace Component
marketplace_data_contractThe data contract drawer in the Data Contracts page.Marketplace Component Contract
marketplace_system_generalThe system General Info card (used by default in the marketplace_system page).Marketplace System
marketplace_component_generalThe component General Info card (used by default in the marketplace_component page).Marketplace Component
marketplace_systemThe page containing the system details page in the marketplace.Marketplace System
marketplace_componentThe page containing the component details page in the marketplace.Marketplace Component
marketplace_search_systemThe content shown inside the card of a system search result in the search page.Marketplace Search Result
marketplace_search_componentThe content shown inside the card of a component search result in the search page.Marketplace Search Result
builder_system_generalThe system General Info card (used by default in the builder_system page).Builder System
builder_component_generalThe component General Info card (used by default in the builder_component page).Builder Component
builder_systemThe page containing the system details page in the builder.Builder System
builder_componentThe page containing the component details page in the builder.Builder Component
builder_entity_contentThe General Info card for all other builder entitiesBuilder Entity
practice_shaper_contentThe page with the type details in the practice shaper.Practice Shaper Entity

The Input Objects are:

  • Marketplace Component: the whole sub-component as sent to the marketplace in the descriptor. This is enriched with an additional field _system that contains the whole system descriptor. If the component has sub-components, an additional _componentsByKind is defined, which contains the sub-components list grouped by kind.
  • Marketplace System: the whole system descriptor as sent to the marketplace. This is enriched with an additional field _components that contains all of its components, _selectedComponents which contains the components selected for an action, _componentsByKind that contains the same list grouped by kind, and _computedInfo that contains the full instance row read from the marketplace.
  • Marketplace Component Contract: the whole component as sent to the marketplace in the descriptor. This is enriched with an additional field _settings that contains the additional contract details, _portWarnings that contains the elaborated warnings associated with the contract, and _system that contains the whole system descriptor.
  • Marketplace Search Result: descriptor that contains the whole descriptor of the search result, _computedInfo that contains extra information such as published_at and the boolean expanded that can be used to show conditionally some data depending if the result card is currently expanded or collapsed.
  • Builder System: an object with a field entity that contains the whole system entity stored in the builder. Another field components contains all of its components.
  • Builder Component: an object with a field entity that contains the whole component entity stored in the builder.
  • Practice Shaper Entity: a complex object with an entity field that contains the entity object stored in the Practice Shaper. Then it has also the following fields:
    • tags: resolves both of the possible entity's tags (the one in metadata, spec, and spec.mesh)
    • urn: this is the resolved URN of the entity
    • taxonomyLabel: this is the taxonomy label of the entity
    • taxonomies: list of relations with other taxonomies

Specific Custom Views for different entities

In case you would like to show different pages for different entities, you can define a custom view for each entity, by specifying the entity in the custom view definition. To identify an entity, you can specify the entity type and/or the entity use case template ID:

  • the entity type is a string that identifies the entity type as defined in the Practice Shaper (like workload or outputport);
  • the entity use case template ID is the string that identifies the use case template of the entity (every descriptor contains a useCaseTemplateId field assigned by the template used to create it).

When multiple custom views are defined for the same page, the system will select the one that matches the entity type and the entity use case template ID, if specified. If no match is found, the system will select the one that matches the use case template ID, if specified, and then the one that matches the entity type. If no match is found, the system will select the one that doesn't specify either the entity type or the entity use case template ID. For example, when rendering a certain page, in case of a descriptor with useCaseTemplateId equal to template1 and entity type equal to workload, the system will select:

  • first, the custom view that specifies the entity type workload and the use case template ID template1;
  • if not found, the custom view that specifies the use case template ID template1;
  • if not found, the custom view that specifies the entity type workload;
  • if not found, the custom view that doesn't specify either the entity type or the entity use case template ID.
  • finally, if no custom view is defined for the page, the system will show the default one defined in the code.

Custom View inclusion

Custom Views allow the platform administrators to include inside a page another custom view. This is useful when you want to reuse a part of the page in different contexts. The inclusion is done by specifying the page ID of the custom view to include in the include field of the custom view definition.

This is very useful in case you want to show the same information on different pages, or for different entities. For example, showing the same system details in some components, without rewriting the same code every time.

The elements that you can include out of the box are:

  • marketplace_component_general: this is the card that shows the general information of a component in the marketplace; Marketplace Component General
  • marketplace_system_general: this is the card that shows the general information of a system in the marketplace; Marketplace System General
  • builder_system_general: this is the card that shows the general information of a system in the builder; Builder System General
  • builder_component_general: this is the card that shows the general information of a component in the builder; Builder Component General

By including the custom views above, you can avoid redefining the same components on different pages, and you can be sure that the information is consistent across the pages.

Registering a Custom View

To register a Custom View, you need to create a new entry in the custom_presentation table in the database. The entry is a YAML string that contains the definition of the custom view, along with all the properties needed to load it correctly.

When registering a Custom View, you need to specify the following fields:

  • page_id: the ID of the page for which the custom view is defined; you can see the list of available pages in the previous section;
  • type_id: the type of the entity for which the custom view is defined;
  • template_id: the use case template ID of the entity for which the custom view is defined;

As explained before, the type_id and template_id fields are used to select the right custom view when multiple custom views are defined for the same page. If they are left empty for a custom view, the system will use it as the default one.

If you have the platform.custom-view.edit permission, you can use the REST API to add a new custom view. A possible request to add a new custom view is:

curl --data-binary @marketplace.yaml \
-H "Content-Type: application/yaml" \
-X PUT \
http://my-witboost-instance/api/platform/custom_presentation/marketplace_system_general

In this example, the file marketplace.yaml contains the YAML representation of the custom view.

In addition, in the example the type and template ID are not specified; however, in case you want to define them, you can add them in the request URL. The URL structure is defined as: /custom_presentation/:pageId/:typeId/:templateId.

note

You need to set the authorization header with a valid JWT token as the Bearer token. In the curl example above, you can just add the token with:

       -H "Authorization: Bearer <your token>"

You can also remove a custom view by sending a DELETE request to the same endpoint:

curl -X DELETE http://my-witboost-instance/api/platform/custom_presentation/marketplace_system_general

Redefining path references

The paths used in components are typically defined with the data object as the starting point. However, some components can redefine the starting point for all of their child components.

One example of this behavior is the grid_sequence component. When given a path property that points to an array in the data structure, the grid_sequence component will have its children refer to the array elements as their root object. This ensures that the reference paths only include the array elements, without the need to express a full path from the original root object. Additionally, the parent component provides an extra property called parent in the new root, which allows access to the original root of the data object.

This flexibility in redefining the starting point is particularly useful when working with complex data structures that involve arrays within arrays.

Here's an example to illustrate this concept:

Root Object

myArray:
- name: Name1
nestedArray:
- description: Description11
- description: Description12
- name: Name2
nestedArray:
- description: Description21
- description: Description22

Custom View

- type: sequence
path: myArray
children:
- type: string
label: Name
path: name
- type: grid_sequence
path: nestedArray
children:
- type: string
label: Description
path: description

In the example above, the sequence component is used to iterate over an array called myArray and then iterate on its child nestedArray. For each element in nestedArray, it displays the description property. The sequence component will use as root objects for its children the elements of myArray. Then, the grid_sequence component will use the elements of nestedArray as root objects for its children.

You can obtain the same result by specifying a new root object for the children of a component by adding a new_root component. The new_root component will redefine the starting point for its children, allowing you to access the properties of the new root object directly.

warning

Some more complex components work by exploring the props of its first-level children (for example table). For this reason, it is not possible to use components like new_root, because it will create a single column instead of the one defined.

Correct

- type: table
children:
- type: string
label: Name
path: metadata.name
- type: string
label: Description
path: metadata.description

Wrong

- type: table
children:
- type: new_root
path: metadata
children:
- type: string
label: Name
path: name
- type: string
label: Description
path: description