MODCFIELDS-39 - Custom Field backend demo

A high-level overview of Custom Fields

What is the custom field?

Custom fields are a set of additional fields that module can define for it is own purposes. The custom fields integration will be covered below in this presentation.


Request dispatching

Usually, OKAPI supports one-to-one connection between module and interface, but for special cases there is a way for multiple modules to implement the same interface by using a special header 'X-Okapi-Module-Id'.  To enable OKAPI to dispatch the request to any number of modules with the same interface just use "interfaceType": "multiple"  in the "Provides" section of the ModuleDescriptor.json file. 

It is assumed that UI will send 'X-Okapi-Module-Id' header along with request to define target module. To find out what modules provide an interface user is looking for, use next request

 _/proxy/tenants/{tenant}/modules?provide={interface}

where {tenant} - the name of the tenant and {interface} - the interface name, in our case `custom-fields`

In the response user receives a list with module ids

And the request to 'mod-users' can be as follows. 

where the 'X-Okapi-Module-Id' header holds the version of the 'mod-users' id.  

To get more information about Multiple interfaces feature follow the link to OKAPI documentation.

The following example shows the part of the ModuleDescriptor.json file with the custom-fields interface section. The full version of ModuleDescriptor.json file for 'mod-users' module can be found via the link.

...
{
      "id": "custom-fields",
      "version": "1.2",
      "interfaceType" : "multiple",
      "handlers": [
        {
          "methods": ["GET"],
          "pathPattern": "/custom-fields",
          "permissionsRequired": ["user-settings.custom-fields.collection.get"]
        },
        {
          "methods": ["POST"],
          "pathPattern": "/custom-fields",
          "permissionsRequired": ["user-settings.custom-fields.item.post"]
        },
        {
          "methods": ["GET"],
          "pathPattern": "/custom-fields/{id}",
          "permissionsRequired": ["user-settings.custom-fields.item.get"]
        },
        {
          "methods": ["PUT"],
          "pathPattern": "/custom-fields/{id}",
          "permissionsRequired": ["user-settings.custom-fields.item.put"]
        },
        {
          "methods": ["PUT"],
          "pathPattern": "/custom-fields",
          "permissionsRequired": ["user-settings.custom-fields.collection.put"]
        },
        {
          "methods": ["DELETE"],
          "pathPattern": "/custom-fields/{id}",
          "permissionsRequired": ["user-settings.custom-fields.item.delete"]
        },
        {
          "methods": ["GET"],
          "pathPattern": "/custom-fields/{id}/stats",
          "permissionsRequired": ["user-settings.custom-fields.item.stats.get"]
        }
      ]
    }
...


Schema overview

The initial schema for custom-fields: 

The current version of the custom-fields supports following types:

  • RADIO_BUTTON
  • SINGLE_CHECKBOX
  • SINGLE_SELECT_DROPDOWN
  • MULTI_SELECT_DROPDOWN
  • TEXTBOX_SHORT

  • TEXTBOX_LONG

Each type have allowed set of the fields that can be defined.

The full schema of customField.json file can be found via the link


Custom Fields validation

The validation of the custom fields is divided onto two parts: validation of the definition and validation of the values assigned to the custom field. 

  1. Definition validation


Each custom-field type has predefined set of allowed properties. 

We also divided definition validation into two groups : common attributes validation and type-specific validation

The Common attributes validation includes following rules:

  • name -  required field,  should not have length more than 65 characters
  • helpText - required field, should not have length more than 100 characters
  • entityType - required field
  • isRepeatable - not required, if not present, will set to false
  • visible - not required, if not present, will set to true
  • required - not required, if not present, will set to false

Also, custom-fields schema does not allow to have additional properties that are not allowed for custom field type, for instance, the following example will not pass definition validation

{
  "visible": true,
  "required": true,
  "type": "SINGLE_SELECT_DROPDOWN",
  "name": "my single select field",
  "order": 5,
  "entityType": "user",
  "helpText": "Select your department",
  "selectField": {
  	"multiSelect": false,
  	"options": {
		"values": [
        	{
         		"id": "opt_1",
         		"value": "Engineering"
        	},
        	{
        	 	"value": "Architecture"
        	}
        ],
  		"sortingOrder": "ASC"
  	}
  },
  "checkboxField": {}
}


The Type-specific validation includes following rules:

RADIO_BUTTON

  • options - required field, max size is values
  • options - max option name length no more than 100 characters
  • multiSelect - required field, should be false for RADIO_BUTTON type
  • defaults - not required, if present, should not have more than 1 value

SINGLE_CHECKBOX

  • default - not required, if not present, will set to false

SINGLE_SELECT_DROPDOWN

  • options - required field, max size is 200 values
  • options - max option name length no more than 100 characters
  • multiSelect - required field, should be false for SINGLE_SELECT_DROPDOWN type
  • defaults - not required, if present, should not have more than 1 value

MULTI_SELECT_DROPDOWN

  • options - required field, max size is 200 values
  • options - max option name length no more than 100 characters
  • multiSelect - required field, should be true for MULTI_SELECT_DROPDOWN type
  • defaults - not required, does not allow duplicates, if present, may have 0 .. n values, where n - values from options 
  1. Values validation

The proposed linkage between custom-field and assigned value is following:

"custom-fields": {
	"refId_1": ["Greek", "Latin"],
	"refId_2": "Yes"
}

where the 'refId_1' - the reference if of the custom field, created during the POST  

For the text custom-field types backed has a restriction on a value length: 

TEXTBOX_SHORT 

  • value - max length no longer than 150 characters

TEXTBOX_LONG

  • value - max length no longer than 1500 characters 

The other custom-field types validate if the custom field is allowed to have a specified value.

Below, you can find and example of assigning custom-field values to a user record

 Click here to expand...
{
  "username": "john_doe",
  "id": "bda67a7d-6088-4a77-b710-b3378d00c33e",
  "active": true,
  "patronGroup": "503a81cd-6c26-400f-b620-14c08943697c",
  "proxyFor": [],
  "personal": {
    "lastName": "Doe",
    "firstName": "John",
    "middleName": "Frederic",
    "email": "test.mail@test.com",
    "addresses": [],
    "preferredContactTypeId": "002"
  },
  "customFields": {
    "text-box-short-refId_1": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis p",
    "text-box-long-refId_1": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc, quis gravida magna mi a libero. Fusce vulputate eleifend sapien. Vestibulum purus quam, scelerisque ut, mollis sed, nonummy id, met",
    "radio-button-refId_1": "Light",
    "single-select-dropdown-button-refId_1": "Architecture",
    "multi-select-dropdown-button-refId_1": ["Design","Engineering"],
    "check-box-button-refId_1": true
  
  }
}

Custom fields Integration to a module

To enable custom-fields for particular module, please, follow the next steps

  • add custom-fields dependency to a target module pom file
<dependency>
	<groupId>org.folio</groupId>
    <artifactId>mod-custom-fields</artifactId>
    <version>1.3.0</version>
</dependency>
To have the newest version of custom-fields please use released versions. The list of released versions is available via the link


  • Include custom-fields interfaces to target module ModuleDescriptor.json file.
{
      "id": "custom-fields",
      "version": "1.2",
      "interfaceType" : "multiple",
      "handlers": [
        {
          "methods": ["GET"],
          "pathPattern": "/custom-fields",
          "permissionsRequired": ["user-settings.custom-fields.collection.get"]
        },
        {
          "methods": ["POST"],
          "pathPattern": "/custom-fields",
          "permissionsRequired": ["user-settings.custom-fields.item.post"]
        },
        {
          "methods": ["GET"],
          "pathPattern": "/custom-fields/{id}",
          "permissionsRequired": ["user-settings.custom-fields.item.get"]
        },
        {
          "methods": ["PUT"],
          "pathPattern": "/custom-fields/{id}",
          "permissionsRequired": ["user-settings.custom-fields.item.put"]
        },
        {
          "methods": ["PUT"],
          "pathPattern": "/custom-fields",
          "permissionsRequired": ["user-settings.custom-fields.collection.put"]
        },
        {
          "methods": ["DELETE"],
          "pathPattern": "/custom-fields/{id}",
          "permissionsRequired": ["user-settings.custom-fields.item.delete"]
        },
        {
          "methods": ["GET"],
          "pathPattern": "/custom-fields/{id}/stats",
          "permissionsRequired": ["user-settings.custom-fields.item.stats.get"]
        }
      ]
    }

The permission name inside of the `permissionsRequired` section can be modified to represent the module purpose.

The example of ModuleDescriptor.json file for 'mod-users' is available here 

  • Add custom-fields script to create a table for storing module-specific custom fields and additional triggers.

"scripts" : [
      {
        "run": "after",
        "snippetPath": "create_custom_fields_table.sql",
        "fromModuleVersion": "1.0"
      }
    ]

The description of 'fromModuleVersion' property can be found here.

The full document of schema.json file for 'mod-users' available via the link 

Use cases and demo

The API Postman collection was created to allow potential users to play around with custom-fields feature.

The provided API collection interacts with  'mod-users

Please note that environment variables file attached below refers to a local setup and may be changed to cooperate with any other environment such as stable env.



Concerns q/a

QuestionAnswer
how will the APIs handle custom fields (including mod-user-import)?In general 'mod-user-import' fetches the data(address types, custom fields definition and patron groups) from 'mod-users', then validates it and if everything is ok returns successful  response, if not, returns list of errors.
how the custom fields can be updated programmatically?you may use prepared collection of API request attached above

why not just put all the custom field info into a single custom_fields table in its own schema rather than having the same table duplicated in multiple modules?

the custom-fields was not designed to be a module, it is rather a library and intended to be closer to the module data
My main concern is that validation of custom fields not slow down bulk insert/update. It would be good if the people developing the custom field support have a test that does a load of about 50,000 users and see what kind off affect it has on performance. However, the problem at the moment is that there are no bulk APIs for insert/update for users. Using the single update HTTP methods may mask performance problems. That is a nice idea, currently, we do not have such kind of test, at least our team did not create it, but in the future it would be really nice to have a JIRA issue for that purpose and make it into account in next planning iteration.
Why does it use Okapi's multiple interfaces feature that requires clients (front-end, other scripts) to pass the complete module id in the X-Okapi-Module-Id header? Front-end and other scripts should work with only some interface version, not a complete module id.

FCFIELDS-4 Spike: Multiple interface, or module specific interfaces?

Related links