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.
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 ID | Page Description | Input Object |
---|---|---|
marketplace_subcomponent_drawer | The drawer that shows the sub-component details of a component in the marketplace. | Marketplace Component |
marketplace_data_contract_drawer | The data contract drawer in the Data Contracts page. | Marketplace Component |
marketplace_system_general | The system General Info card (used by default in the marketplace_system page). | Marketplace System |
marketplace_component_general | The component General Info card (used by default in the marketplace_component page). | Marketplace Component |
marketplace_system | The page containing the system details page in the marketplace. | Marketplace System |
marketplace_component | The page containing the component details page in the marketplace. | Marketplace Component |
marketplace_search_system | The content shown inside the card of a system search result in the search page. | Marketplace Search Result |
marketplace_search_component | The content shown inside the card of a component search result in the search page. | Marketplace Search Result |
marketplace_zoom_in_system | The content shown inside the Overview tab of the ZoomIn page drawer | Marketplace ZoomIn System |
marketplace_zoom_in_component | The content shown inside the Output Ports tab of the ZoomIn page drawer | Marketplace ZoomIn Component |
builder_system_general | The system General Info card (used by default in the builder_system page). | Builder System |
builder_component_general | The component General Info card (used by default in the builder_component page). | Builder Component |
builder_system | The page containing the system details page in the builder. | Builder System |
builder_component | The page containing the component details page in the builder. | Builder Component |
builder_entity_content | The General Info card for all other builder entities | Builder Entity |
practice_shaper_content | The 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. If the component is a data contract (or takes part in a parent data contract), an additional_dataContractStatus
is defined, containing informations about the last data contract evaluation result, such astargetValidations
andoutcome
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 Search Result
:descriptor
that contains the whole descriptor of the search result,_computedInfo
that contains extra information such aspublished_at
and the booleanexpanded
that can be used to show conditionally some data depending on if the result card is currently expanded or collapsed.Builder System
: an object with a fieldentity
that contains the whole system entity stored in the builder. Another fieldcomponents
contains all of its components.Builder Component
: an object with a fieldentity
that contains the whole component entity stored in the builder.Practice Shaper Entity
: a complex object with anentity
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 inmetadata
,spec
, andspec.mesh
)urn
: this is the resolved URN of the entitytaxonomyLabel
: this is the taxonomy label of the entitytaxonomies
: list of relations with other taxonomies
Marketplace ZoomIn System
: the whole system descriptor as sent to the marketplace. This is enriched with an additional field_computedInfo
that contains the full instance row read from the marketplace.Marketplace ZoomIn Component
: the whole component descriptor as sent to the marketplace. This is enriched with an additional field_computedInfo
that contains the full instance row read from the marketplace.
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
oroutputport
); - 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 IDtemplate1
; - 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_system_general: this is the card that shows the general information of a system in the marketplace;
- builder_system_general: this is the card that shows the general information of a system in the builder;
- builder_component_general: this is the card that shows the general information of a component in the builder;
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
.
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.
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