Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Throughout these documentation I've referred to SimpleSearch. This is our first WidgetType, and it's The SimpleSearch widget is designed to do simple lookup and display of information from backends implementing the same query style as ERM.I'm now going to walk you through some of the setup we have mod-agreements and mod-licenses.

This page walks through the setup for SimpleSearch, in order to help illustrate some of thisillustrate how a widget is developed.

Note

This page is intended to help illustrate how a Type/Definition interaction looks, in order to aid with the writing and maintaining of new WidgetDefinitions for any WidgetType. If you're looking for specific documentation on the fields available to you in the SimpleSearch type, or how widgets of this type look/work, refer instead to this page.

WidgetType

This is going to dig a bit deeper into the type JSON for SimpleSearch. The WidgetType is quite large, so I'm going to break it the following explanation is broken down into component sections and talk about which describe what they do and how you they can use them be used to define a WidgetDefinition which conforms to this Type.

Note
Please note that thise this is still under development , so any of the specifics are and subject to change.

At the top level, this the schema for the SimpleSearch WidgetType (this which does not include the fields at the level above for name/version) looks like the following:

...

This specifies some simple information about the type, as well as any required information a WidgetDefinition must contain in order to be a valid Definition for this Type. This is where anything the frontend absolutely requires for basic functionality has to be contained.

Let's look at Examining some of the properties:

Code Block
"properties": {
	"baseUrl": {
		"type": "string",
		"description": "The base url queries built with this widget will go to"
	},
	"results": {
		"$ref": "#/$defs/results"
	},
	"filters": {
		"$ref": "#/$defs/filters"
	},
	"sort": {
		"$ref": "#/$defs/sort"
	},
	"configurableProperties": {
		"$ref": "#/$defs/configurableProperties"
	}
}

As you can seebe seen, some of the properties may be very simple fields, such as baseUrl, and some of the properties may require more complicated objects. In this case, most of the top-level properties require more detailed information, so to keep the JSON schema as efficient and flat as possible, this information is split out into a "$defs" section. Let's look next

Looking at the "results" property:

...

SimpleSearch also has similar fields for `filters` and `sort`, determining the fields which a user can define filters on, and sort on. The valueType in the filter column will be used to decide what kind of field entry the user will see in the widget creation form. For a Date they will see a FOLIO datepicker, for a String they will see a TextField etc.

Finally lets look looking at some of the other options the SimpleSearch WidgetType gives us for the Definitions:

Code Block
"configurableProperties": {
	"type": "object",
	"title": "Other properties",
	"description": "A collection of other properties which can be configured/made configurable",
	"additionalProperties": false,
	"properties": {
		"urlLink": {
			"$ref": "#/$defs/urlLink"
		},
		"numberOfRows": {
			"$ref": "#/$defs/numberOfRows"
		}
	}
}

We use these as These properties provide a place to hold other configurable fields, where we know things such as what type of data we're expecting. The urlLink will be used to display a link at the bottom of the simpleSearch widget, which the user can configure to take them to the same query in the SASQ screen of whatever app is relevant. The numberOfRows is how we specify a maximum number of rows for the fetch to return. See below for how this would be configured in a definition.

WidgetDefinition

A WidgetDefinition ready to be accepted into the database for this type might look like the following:

Code Block
{
	"type": {
		"name": "SimpleSearch",
		"version": "v1.01.0"
	},
	"version": "v1.01.0",
	"name":"ERM Agreements",
	"definition": {
		...
	}
}

(This top level shape will be shared by definitions for ALL widget types)

We will get to the definition in a moment, but first notice that we use Notice that text fields are used here for the "type" field. We will then use these These will be used to attempt to resolve which type this definition conforms to (If in fact if, after resolution, we do not find the relevant type, or the "definition" field fails validation then this will error out).

We There is also have a "version" field. This will be used to resolve definitions in case breaking changes are made to a widgetDefinition, eg columns removed or API paths changing. The idea is that widgets created using the old version of the widgetDefinition will not immediately fail .(The concept of what we should do here(the appropriate action to take in this scenario, perhaps rendering a red "attention needed" widget in place of out of date ones or a simple wanring warning MessageBanner, will be later determined by user use cases and stories).

The definition section is what contains the meat of the WidgetDefinition, the actual JSON conforming to the Type.

Code Block
{
	"baseUrl":"/erm/sas",
	"results": {
		"columns": [
			{
				"name":"agreementName",
				"label": "Agreement name",
				"accessPath":"name",
				"valueType": "String"
			},
			{
				"name":"startDate",
				"label": "Start date",
				"accessPath":"startDate",
				"valueType": "Date"
			},
			{
				"name":"endDate",
				"label": "End date",
				"accessPath":"endDate",
				"valueType": "Date"
			},
			{
				"name":"agreementStatus",
				"label": "Status",
				"accessPath":"agreementStatus.value",
				"valueType": "String"
			}
		]
	},
	"filters": {
		"columns": [
			{
				"name":"agreementName",
				"label": "Agreement name",
				"filterPath":"name",
				"valueType": "String",
				"comparators": ["=~", "!~"]
			},
			{
				"name":"agreementStatus",
				"label": "Agreement status",
				"filterPath":"agreementStatus.value",
				"valueType": "Enum",
				"enumValues": [
					{"value": "active", "label": "Active"},
					{"value": "closed", "label": "Closed"},
					{"value": "in_negotiation", "label": "In negotiation"}
				],
				"comparators": ["==", "!="]
			},
			{
				"name":"startDate",
				"label": "Start date",
				"filterPath":"startDate",
				"valueType": "Date",
				"comparators": ["==", "!=", ">", ">=", "<", "<="]
			},
			{
				"name":"endDate",
				"label": "End date",
				"filterPath":"endDate",
				"valueType": "Date",
				"comparators": ["==", "!=", ">", ">=", "<", "<=", "isNull", "isNotNull"]
			},
			{
				"name":"internalContact",
				"label": "Internal contact",
				"filterPath":"contacts.user",
				"valueType": "UUID",
				"comparators": ["==", "!="]
			}
		]
	},
	"sort": {
		"columns": [
			{
				"name":"id",
				"sortPath":"id",
				"sortTypes": ["asc"]
			},
			{
				"name":"agreementName",
				"sortPath":"name",
				"sortTypes": ["asc", "desc"]
			},
			{
				"name":"startDate",
				"sortPath":"startDate",
				"sortTypes": ["asc", "desc"]
			},
			{
				"name":"endDate",
				"sortPath":"endDate",
				"sortTypes": ["asc", "desc"]
			}
		]
	},
	"configurableProperties": {
		"urlLink": {
			"configurable": true
		},
		"numberOfRows": {
			"configurable": false,
			"defValue": 5
		}
	}
}

There 's is a lot considerable amount of information there, so I'm going to break down some of the headlines.in the widget definition. The key aspects are:

Results

This section is all about display in the final table. To that end the value types allowed are all from the following list 

Code Block
["String", "Integer", "Float", "Boolean", "BooleanDate", "DateLink"]

Notice for example that the valueType for agreementStatus is "String", whereas later on the same path is described as an "Enum". We will look more at the filters later, but for right now all you need to know is The filters are described in more detail below, but it is important to note that a valueType String means that the resulting field will be displayed as simple text in the resulting table.

Likewise we use "Boolean" and "Date" types are used to drive display of either a FormattedUTCDate, or a tick/cross for true/false., and "Link" is used to attempt to generate

These columns are then used to populate a component which allows us offers the ability to pick one or several of these to form the columns for our display table in the output. The label will be the default label displayed for that column, although this can be overwritten.

...

This section drives both how we dynamically create the filter sections are created dynamically as well as how we build the filter part of the final query . As mentioned above lets look is built. Looking first at the agreementStatus entry:

Code Block
{
	"name":"agreementStatus",
	"label": "Agreement status",
	"filterPath":"agreementStatus.value",
	"valueType": "Enum",
	"enumValues": [
		{"value": "active", "label": "Active"},
		{"value": "closed", "label": "Closed"},
		{"value": "in_negotiation", "label": "In negotiation"}
	],
	"comparators": ["==", "!="]
}

As before we have there is a name and label, used to help the user while they build their widget configuration in the form. Here we have In this case it is specified that the agreement status has type "Enum". This is because agreementStatus is not a string, but actually a closed list of possible values. This setup will mean that the user will see a dropdown list of the specified values.

Note

At some point we may offer a more complicated WidgetType (or version of SimpleSearch) maybe offered that can look up these values up at the point where the user is building their widget.

Since we're just saving this to ultimately this is saved as a JSON string eventually , it wouldn't break anything to specify this as a "String" type. However that would necessitate that your users know each of the possible values for this field, as well as any differences between the filter filter value and how it's displayed to them usually . In fact (i.e. the UI label used for the value). Equally it wouldn't break anything to put type Date, but it would then render a datePicker component and return strings of form "2021-03-19", so wouldn't likely be so useful. 
However there's nothing stopping you from having an

For example, it would be possible to specify a valueType "Enum" for n set Dates in a date field, say: 

Code Block
"enumValues": [
	{"value": "2020-12-25", "label": "Christmas 2020"},
	{"value": "2019-12-25", "label": "Christmas 2019"},
	{"value": "2018-12-25", "label": "Christmas 2018"},
],

The point of stating all this is just to illustrate This illustrates that the actual type of the underlying data isn't what you're specifying here, it's all relating is being specified here, instead these properties all relate to the configurable form and display in the UI.

The final part of this section is the comparators. For SimpleSearch these can be chosen from the list (although this is at present not enforced):

...

This simply needs the path, and whether we're offering what sort options (in this case ascending/descending options) are being offered. If there is only one sortType for a single sortEntry then no options are offered to the user in the form and that single value is used instead.

...

Code Block
"configurableProperties": {
	"urlLink": {
		"configurable": true
	},
	"numberOfRows": {
		"configurable": false,
		"defValue": 5
	}
}

Here you can see that , the urlLink is set to be configurable, whereas numberOfRows is non-configruable, and has a defaultValue. This will remove that entry on the dynamic widget form. The Type schema is set up to enforce defValue if configurable is set to false. In the case where a defValue is set and configurable is set to true this will manifest as a default value in the initial form creation.