The aim of this document is to:

  • Explain what OperatorFabric is about and define the concepts it relies on

  • Give a basic tour of its features from a user perspective

1. Introduction

To perform their duties, an operator has to interact with multiple applications (perform actions, watch for alerts, etc.), which can prove difficult if there are too many of them.

The idea is to aggregate all the notifications from all these applications into a single screen, and to allow the operator to act on them if needed.

Feed screen layout

These notifications are materialized by cards sorted in a feed according to their period of relevance and their severity. When a card is selected in the feed, the right-hand pane displays the details of the card.

In addition, the cards will also translate as events displayed on a timeline at the top of the screen.

Part of the value of OperatorFabric is that it makes the integration very simple on the part of the third-party applications. To start publishing cards to users in an OperatorFabric instance, all they have to do is:

  • Register as a publisher through the "Businessconfig" service and provide a "bundle" containing handlebars templates defining how cards should be rendered, i18n info etc.

  • Publish cards as json containing card data through the card publication API

OperatorFabric will then:

  • Dispatch the cards to the appropriate users (by computing the actual users who should receive the card from the recipients rules defined in the card)

  • Take care of the rendering of the cards

  • Display relevant information from the cards in the timeline

A card is not only information, it could be question(s) the operator has to answer. When the operator is responding, a card is emitted to the sender of the initial card and the response could be seen by other operators.

Feed screen layout

2. Sending cards

The Cards Publication Service exposes a REST API through which third-party applications, or "publishers" can post cards to OperatorFabric. It then handles those cards:

  • Time-stamping them with a "publishDate"

  • Sending them to the message broker (RabbitMQ) to be delivered in real time to the appropriate operators

  • Persisting them to the database (MongoDB) for later consultation

2.1. Card Structure

Cards are represented as Json objects. The technical design of cards is described in the cards api documentation . A card correspond to the state of a Process in OperatorFabric.

2.1.1. Technical Information of the card

Those attributes are used by OperatorFabric to manage how cards are stored, to whom and when they’re sent.

2.1.1.1. Mandatory information

Below, the json technical key is in the '()' following the title.

Publisher (publisher)

The publisher field bears the identifier of the emitter of the card, be it an entity or an external service.

Process (process)

This field indicates which process the card is attached to. This information is used to resolve the presentation resources (bundle) used to render the card and card details.

Process Version (processVersion)

The rendering of cards of a given process can evolve over time. To allow for this while making sure previous cards remain correctly handled, OperatorFabric can manage several versions of the same process. The processVersion field indicate which version of the process should be used to retrieve the presentation resources (i18n, templates, etc.) to render this card.

Process Instance Identifier (processInstanceId)

A card is associated to a given process, which defines how it is rendered, but it is also more precisely associated to a specific instance of this process. The processInstanceId field contains the unique identifier of the process instance.

2.1.1.2. State in the process (state)

The card represent a specific state in the process. In addition to the process, this information is used to resolve the presentation resources used to render the card and card details.

Start Date (startDate)

Start date of the active period of the card (process business time).

Severity (severity)

The severity is a core principe of the OperatorFabric Card system. There are 4 severities available. A color is associated in the GUI to each severity. Here the details about severity and their meaning for OperatorFabric:

  1. ALARM: represents a critical state of the associated process, need an action from the operator. In the UI, the card is red;

  2. ACTION: the associated process need an action form operators in order to evolve correctly. In the UI, the card is orange;

  3. COMPLIANT: the process related to the card is in a compliant status. In the UI, the card is green.;

  4. INFORMATION: give information to the operator. In the UI, the card is blue.

Title (title)

This attribute is display as header of a card in the feed of the GUI. It’s the main User destined Information of a card. The value refer to an i18n value used to localize this information.

Summary (summary)

This attribute is display as a description of a card in the feed of the GUI, when the card is selected by the operator. It’s completing the information of the card title. The value refer to an i18n value used to localize this information.

2.1.1.3. Recipient (recipient)

Declares to whom the card is send. For more details about way recipient works see Display rules . Without recipient declaration a card is useless in OperatorFabric system.

2.1.1.4. Optional information
End Date (endDate)

End date of the active period of the card (process business time).

Tags (tag)

Tags are intended as an additional way to filter cards in the feed of the GUI.

EntityRecipients (entityRecipients)

Used to send cards to entity : all users members of the listed entities who have the right for the process/state of the card will receive it. If this field is used in conjunction with groups recipients, to receive the cards :

  • users must be members of one of the entities AND one of the groups to receive the cards.

OR

  • users must be members of one of the entities AND have the right for the process/state of the card.

Last Time to Decide (lttd)

Fixes the moment until when a response is possible for the card. After this moment, the response button won’t be useable. When lttd time is approaching, a clock is visible on the card in the feed with the residual time. The lttd time can be set for cards that don’t expect any response

2.1.1.5. Business period

We define the business period as starting form startDate to endDate. The card will be visible on the UI if the business period overlap the user chosen period (i.e the period selected on the timeline). If endDate is not set, the card will be visible as soon as the startDate is before the end of the chosen period.

2.1.1.6. Store information
uid (uid)

Unique identifier of the card in the OperatorFabric system. This attribute can be sent with card, but by default it’s managed by OperatorFabric.

id (id)

State id of the associated process, determined by OperatorFabric can be set arbitrarily by the publisher. The id is determined by 'OperatorFabric' as follow : process.processIntanceId

Publish Date (publishDate)

Indicates when the card has been registered in OperatorFabric system. This is a technical information exclusively managed by OperatorFabric.

2.1.2. User destined Information of the card

There are two kind of User destined information in a card. Some are restricted to the card format, others are defined by the publisher as long as there are encoded in json format.

2.1.2.1. in Card Format
Title (title)

See Title .

Summary (summary)

See Summary .

2.1.2.2. Custom part
Data (data)

Determines where custom information is store. The content in this attribute, is purely publisher choice. This content, as long as it’s in json format can be used to display details. For the way the details are displayed, see below.

You must not use dot in json field names. In this case, the card will be refused with following message : "Error, unable to handle pushed Cards: Map key xxx.xxx contains dots but no replacement was configured!""

2.1.3. Presentation Information of the card

2.1.3.1. details (details)

This attribute is a string of objects containing a title attribute which is i18n key and a template attribute which refers to a template name contained in the publisher bundle. The bundle in which those resources will be looked for is the one corresponding to the version declared in the card for the current process . If no resource is found, either because there is no bundle for the given version or there is no resource for the given key, then the corresponding key is displayed in the details section of the GUI.

example:

The TEST process only has a 0.1 version uploaded in the current OperatorFabric system. The details value is [{"title":{"key":"first.tab.title"},"template":"template0"}].

If the processVersion of the card is 2 then only the title key declared in the details array will be displayed without any translation, i.e. the tab will contains TEST.2.first.tab.title and will be empty. If the l10n for the title is not available, then the tab title will be still TEST.2.first.tab.title but the template will be computed and the details section will display the template content.

2.1.3.2. TimeSpans (timeSpans)

When the simple startDate and endDate are not enough to characterize your process business times, you can add a list of TimeSpan to your card. TimeSpans are rendered in the timeline component as cluster bubbles. This has no effect on the feed content.

example :

to display the card two times in the timeline you can add two TimeSpan to your card:

{
	"publisher":"TSO1",
	"publisherVersion":"0.1",
	"process":"process",
	"processInstanceId":"process-000",
	"startDate":1546297200000,
	"severity":"INFORMATION",
	...
	"timeSpans" : [
        {"start" : 1546297200000},
        {"start" : 1546297500000}
    ]

}

In this sample, the card will be displayed twice in the time line. The card start date will be ignored.

For timeSpans, you can specify an end date but it is not implemented in OperatorFabric (it was intended for future uses but it will be deprecated).

2.2. Cards Examples

Before detailing the content of cards, let’s show you what cards look like through few examples of json.

2.2.1. Minimal Card

The OperatorFabric Card specification defines mandatory attributes, but some optional attributes are needed for cards to be useful in OperatorFabric. Let’s clarify those point through few examples of minimal cards and what happens when they’re used as if.

2.2.1.1. Send to One User

The following card contains only the mandatory attributes.

{
	"publisher":"TSO1",
	"processVersion":"0.1",
	"process":"process",
	"processInstanceId":"process-000",
	"state":"myState",
	"startDate":1546297200000,
	"severity":"INFORMATION",
	"title":{"key":"card.title.key"},
	"summary":{"key":"card.summary.key"},
	"userRecipients": ["tso1-operator"]

}

This an information about the process instance process-000 of process process, sent by TSO1. The title and the summary refer to i18n keys defined in the associated i18n files of the process. This card is displayable since the first january 2019 and should only be received by the user using the tso1-operator login.

2.2.1.2. Send to several users
Simple case (sending to a group)

The following example is nearly the same as the previous one except for the recipient.

{
	"publisher":"TSO1",
	"processVersion":"0.1",
	"process":"process",
	"processInstanceId":"process-000",
	"state":"myState",
	"startDate":1546297200000,
	"severity":"INFORMATION",
	"title":{"key":"card.title.key"},
	"summary":{"key":"card.summary.key"},
	"groupRecipients": ["TSO1"]
}

Here, the recipient is a group, the TSO1. So all users who are members of this group will receive the card.

Simple case (sending to a group and an entity)

The following example is nearly the same as the previous one except for the recipient.

{
	"publisher":"TSO1",
	"processVersion":"0.1",
	"process":"process",
	"processInstanceId":"process-000",
	"state":"myState",
	"startDate":1546297200000,
	"severity":"INFORMATION",
	"title":{"key":"card.title.key"},
	"summary":{"key":"card.summary.key"},
	"groupRecipients": ["TSO1"],
	"entityRecipients" : ["ENTITY1"]
}

Here, the recipients are a group and an entity, the TSO1 group and ENTITY1 entity. So all users who are both members of this group and this entity will receive the card.

Simple case (sending to an entity)

The following example is nearly the same as the previous one except for the recipient.

{
	"publisher":"TSO1",
	"processVersion":"0.1",
	"process":"process",
	"processInstanceId":"process-000",
	"state":"myState",
	"startDate":1546297200000,
	"severity":"INFORMATION",
	"title":{"key":"card.title.key"},
	"summary":{"key":"card.summary.key"},
	"entityRecipients" : ["ENTITY1"]
}

Here, the recipient is an entity and there is no more groups. So all users who has the right perimeter and who are members of this entity will receive the card. More information on perimeter can be found in user documentation

Complex case

If this card need to be viewed by a user who is not in the TSO1 group, it’s possible to tune more precisely the definition of the recipient. If the tso2-operator needs to see also this card, the recipient definition could be(the following code details only the recipient part):

"groupRecipients": ["TSO1"],
"userRecipients": ["tso2-operator"]

So here, all the users of the TSO1 group will receive the INFORMATION as should the tos2-operator user.

Another example, if a card is destined to the operators of TSO1 and TSO2 and needs to be also seen by the admin, the recipient configuration looks like:

"groupRecipients": ["TSO1", "TSO2"],
"userRecipients": ["admin"]

There is and alternative way to declare recipients of card, this syntax is more complex and will be deprecated:

"recipient":{
	"type":"UNION",
	"recipients":[
		{ "type": "GROUP", "identity":"TSO1"},
		{ "type": "USER", "identity":"tso2-operator"}
		]
	}

2.2.2. Regular Card

The previous cards were nearly empty regarding information carrying. In fact, cards are intended to contain more information than a title and a summary. The optional attribute data is here for that. This attribute is destined to contain any json object. The creator of the card is free to put any information needed as long as it’s in a json format.

2.2.2.1. Full of Hidden data

For this example we will use our previous example for the TSO1 group with a data attribute containing the definition of a json object containing two attributes: stringExample and numberExample.

{
	"publisher":"TSO1",
	"processVersion":"0.1",
	"process":"process",
	"processInstanceId":"process-000",
	"state":"myState",
	"startDate":1546297200000,
	"severity":"INFORMATION",
	"title":{"key":"card.title.key"},
	"summary":{"key":"card.summary.key"},
	"userRecipients": ["tso1-operator"],
	"data":{
		"stringExample":"This is a not so random string of characters.",
		"numberExample":123
		}

}

This card contains some data but when selected in the feed nothing more than the previous example of card happen because there is no rendering configuration.

2.2.2.2. Fully useful

When a card is selected in the feed (of the GUI), the data is displayed in the detail panel. The way details are formatted depends on the template contained in the bundle associated with the process as described here . To have an effective example without to many actions to performed, the following example will use an already existing configuration.The one presents in the development version of OperatorFabric, for test purpose(TEST bundle).

At the card level, the attributes in the card telling OperatorFabric which template to use is the details attributes.

{
	"publisher":"TEST_PUBLISHER",
	"processVersion":"1",
	"process":"TEST",
	"processInstanceId":"process-000",
	"state":"myState",
	"startDate":1546297200000,
	"severity":"INFORMATION",
	"title":{"key":"process.title"},
	"summary":{"key":"process.summary"},
	"userRecipients": ["tso1-operator"]
	"data":{"rootProp":"Data displayed in the detail panel"},
	"details":[{"title":{"key":"process.detail.tab.first"}, "templateName":"template1"}]

}

So here a single custom data is defined and it’s rootProp. This attribute is used by the template called by the details attribute. This attribute contains an array of json object containing an i18n key and a template reference. Each of those object is a tab in the detail panel of the GUI.

3. Card rendering

As stated above, third applications interact with OperatorFabric by sending cards.

The Businessconfig service allows them to tell OperatorFabric for each process how these cards should be rendered including translation if several languages are supported. Configuration is done via files zipped in a "bundle", these files are send to OperatorFabric via a REST end point.

In addition, it lets third-party applications define additional menu entries for the navbar (for example linking back to the third-party application) that can be integrated either as iframe or external links.

3.1. Declaring a Process and its configuration

The business configuration for processes is declared in the form of a bundle, as described below. Once this bundle fully created, it must be uploaded to the server through the Businessconfig service.

The way configuration is done is explained with examples before a more technical review of the configuration details. The following instructions describe tests to perform on OperatorFabric to understand how customization is working in it. The card data used here are sent automatically using a script as described here

A bundle contains all the configuration regarding a given business process, describing for example the various steps of the process but also how the associated cards and card details should be displayed. Those tar.gz archives contain a descriptor file named config.json, eventually some css files, i18n files and handlebars templates to do so.

For didactic purposes, in this section, the businessconfig name is BUNDLE_TEST (to match the parameters used by the script). This bundle is localized for en and fr.

As detailed in the Businessconfig core service README the bundle contains at least a metadata file called config.json, a css folder, an i18n folder and a template folder. All elements except the config.json file are optional.

The files of this example are organized as below:

bundle
├── config.json
├── css
│   └── bundleTest.css
├── i18n
│   ├── en.json
│   └── fr.json
└── template
    ├── en
    │   ├── template1.handlebars
    │   └── template2.handlebars
    └── fr
        ├── template1.handlebars
        └── template2.handlebars

3.1.1. The config.json file

It’s a description file in json format. It lists the content of the bundle.

example

{
  "id": "TEST",
  "version": "1",
  "uiVisibility": {
    "monitoring": true,
    "logging": true,
    "calendar": true
  },
  "name": "process.label",
  "defaultLocale": "fr",
  "menuLabel": "menu.label",
  "menuEntries": [
    {
      "id": "uid test 0",
      "url": "https://opfab.github.io/",
      "label": "menu.first",
      "linkType" : "BOTH"
    },
    {
      "id": "uid test 1",
      "url": "https://www.lfenergy.org/",
      "label": "menu.second",
      "linkType" : "TAB"
    }
  ],
  "states": {
    "firstState": {
      "name":  "state.label",
      "color": "blue",
      "details": [
        {
          "title": {
            "key": "template.title"
          },
          "templateName": "operation"
        }
      ],
      "acknowledgementAllowed": false
    }
  }
}
  • id: id of the process

  • name: process name (i18n key);

  • version: enable the correct display, even the old ones as all versions are stored by the server. Your card has a version field that will be matched to businessconfig configuration for correct rendering ;

  • states : list the available states; actions and templates are associated to states, in the same way is the possibility of making the cards enabled for being acknowledged by user;

  • menuLabel in the main bar menu as i18nLabelKey: optional, used if the businessconfig service add one or several entry in the OperatorFabric main menu bar, see the menu entries section for details;

  • extra menu entries as menuEntries: optional, see below for the declaration format of objects of this array, see the menu entries section for details;

The mandatory declarations are id,'name' and version attributes.

See the Businessconfig API documentation for details.

3.1.2. i18n

There are two ways of i18n for businessconfig service. The first one is done using l10n files which are located in the i18n folder, the second one throughout l10n name folder nested in the template folder.

The i18n folder contains one json file per l10n.

These localisation is used for integration of the businessconfig service into OperatorFabric, i.e. the label displayed for the process , the state , the label displayed for each tab of the details of the card, the label of the actions…​

3.1.2.1. Template folder

The template folder must contain localized folder for the i18n of the card details. This is why in our example, as the bundle is localized for en and fr language, the template folder contains a en and a fr folder.

i18n file

If there is no i18n file or key is missing, the i18n key is displayed in OperatorFabric.

The choice of i18n keys is left to the Businessconfig service maintainer. The keys are referenced in the following places:

  • config.json file:

    • i18nLabelKey: key used for the label for the businessconfig service displayed in the main menu bar of OperatorFabric;

    • label of menu entry declaration: key used to l10n the menu entries declared by the Businessconfig party in the bundle;

  • card data: values of card title and card summary refer to i18n keys as well as key attribute in the card detail section of the card data.

example

So in this example the process is named Bundle Test with BUNDLE_TEST technical name. The bundle provide an english and a french l10n.

The example bundle defined an new menu entry given access to 3 entries. The title and the summary have to be l10n, so needs to be the 2 tabs titles.

The name of the process as displayed in the main menu bar of OperatorFabric. It will have the key "businessconfig-name-in-menu-bar". The english l10n will be Bundle Test and the french one will be Bundle de test.

A name for the three entries in the process entry menu. Their keys will be in order "first-menu-entry", "b-menu-entry" and "the-other-menu-entry" for an english l10n as Entry One, Entry Two and Entry Three and in french as Entrée une, Entrée deux and Entrée trois.

The title for the card and its summary. As the card used here are generated by the script of the cards-publication project we have to used the key declared there. So they are respectively process.title and process.summary with the following l10ns for english: Card Title and Card short description, and for french l10ns: Titre de la carte and Courte description de la carte.

A title for each (two of them) tab of the detail cards. As for card title and card summary, those keys are already defined by the test script. There are "process.detail.tab.first" and "process.detail.tab.second". For english l10n, the values are First Detail List and Second Detail List and for the french l10n, the values are Première liste de détails and Seconde liste de détails.

Here is the content of en.json

{
	"businessconfig-name-in-menu-bar":"Bundle Test",
		"first-menu-entry":"Entry One",
		"b-menu-entry":"Entry Two",
		"the-other-menu-entry":"Entry Three",
		"process":{
			"title":"Card Title",
			"summary":"Card short description",
			"detail":{
				"tab":{
					"first":"First Detail List",
					"second":"Second Detail List"
				}
			}
		}
}

Here the content of fr.json

{
	"businessconfig-name-in-menu-bar":"Bundle de test",
		"first-menu-entry":"Entrée une",
		"b-menu-entry":"Entrée deux",
		"the-other-menu-entry":"Entrée trois",
		"process":{
			"title":"Titre de la carte",
			"summary":"Courte description de la carte",
			"detail":{
				"tab":{
					"first":"Première liste de détails",
					"second":"Deuxième liste de détails"
				}
			}
		}
}

Once the bundle is correctly uploaded, the way to verify if the i18n have been correctly uploaded is to use the GET  method of businessconfig api for i18n file.

The endpoint is described here .

The locale language, the version of the bundle and the technical name of the businessconfig party are needed to get json in the response.

To verify if the french l10n data of the version 1 of the BUNDLE_TEST businessconfig party we could use the following command line

curl -X GET "http://localhost:2100/businessconfig/BUNDLE_TEST/i18n?locale=fr&version=1" -H "accept: application/json"

The service response with a 200 status and with the json corresponding to the defined fr.json file show below.

{
"businessconfig-name-in-menu-bar":"Bundle de test",
"first-menu-entry":"Entrée une",
"b-menu-entry":"Entrée deux",
"the-other-menu-entry":"Entrée trois",
"tests":{
	"title":"Titre de la carte",
	"summary":"Courte description de la carte",
	"detail":{
		"tab":{
			"first":"Première liste de détails",
			"second":"Deuxième liste de détails"
			}
		}
}
}
3.1.2.2. Processes and States

Processes and their states allows to match a Businessconfig Party service process specific state to a list of templates for card details and actions allowing specific card rendering for each state of the business process.

The purpose of this section is to display elements of businessconfig card data in a custom format.

Regarding the card detail customization, all the examples in this section will be based on the cards generated by the script existing in the Cards-Publication project. For the examples given here, this script is run with arguments detailed in the following command line:

$OPERATOR_FABRIC_HOME/services/core/cards-publication/src/main/bin/push_card_loop.sh

where:

  • $OPERATOR_FABRIC_HOME is the root folder of OperatorFabric where tests are performed;

  • BUNDLE_TEST is the name of the Businessconfig party;

  • tests is the name of the process referred by published cards.

configuration

The process entry in the configuration file is a dictionary of processes, each key maps to a process definition. A process definition is itself a dictionary of states, each key maps to a state definition. A state is defined by:

  • a list of details: details are a combination of an internationalized title (title), css class styling element (titleStyle) and a template reference

  • a dictionary of actions: actions are described below

Templates

For demonstration purposes, there will be two simple templates. For more advance feature go to the section detailing the handlebars templates in general and helpers available in OperatorFabric. As the card used in this example are created above , the bundle template folder needs to contain 2 templates: template1.handlebars and template2.handlebars.

examples of template (i18n versions)

/template/en/template1.handlers

<h2>Template Number One</h2>
<div class="bundle-test">'{{card.data.level1.level1Prop}}'</div>

/template/fr/template1.handlers

<h2>Patron numéro Un</h2>
<div class="bundle-test">'{{card.data.level1.level1Prop}}'</div>

Those templates display a l10n title and an line containing the value of the scope property card.level1.level1Prop which is This is a root property.

/template/en/template2.handelbars

<h2>Second Template</h2>
<ul class="bundle-test-list">
	{{#each card.data.level1.level1Array}}
		<li class="bunle-test-list-item">{{this.level1ArrayProp}}</li>
	{{/each}}
</ul>

/template/fr/template2.handelbars

<h2>Second patron</h2>
<ul class="bundle-test-list">
	{{#each card.data.level1.level1Array}}
		<li class="bunle-test-list-item">{{this.level1ArrayProp}}</li>
	{{/each}}
</ul>

Those templates display also a l10n title and a list of numeric values from 1 to 3.

CSS

This folder contains regular css files. The file name must be declared in the config.json file in order to be used in the templates and applied to them.

Examples

As above, all parts of files irrelevant for our example are symbolised by a character.

Declaration of css files in config.json file

{
	…
    "states" : {
            "state1" : {
                  …
	                "styles":["bundleTest"]
	…
}

CSS Class used in ./template/en/template1.handlebars

	…
	<div class="bundle-test">'{{card.data.level1.level1Prop}}'</div>
	…

As seen above, the value of {{card.data.level1.level1Prop}} of a test card is This is a level1 property

Style declaration in ./css/bundleTest.css

.h2{
	color:#fd9312;
	font-weight: bold;
}

Expected result

Formatted root property
3.1.2.3. Upload

For this, the bundle is submitted to the OperatorFabric server using a POST http method as described in the Businessconfig Service API documentation .

Example :

cd $BUNDLE_FOLDER
curl -X POST "http://localhost:2100/businessconfig" -H  "accept: application/json" -H  "Content-Type: multipart/form-data" -F "file=@bundle-test.tar.gz;type=application/gzip"

Where:

  • $BUNDLE_FOLDER is the folder containing the bundle archive to be uploaded.

  • bundle-test.tar.gz is the name of the uploaded bundle.

These command line should return a 200 http status response with the details of the of the bundle in the response body such as :

{
  "menuEntriesData": [
    {
      "id": "uid test 0",
      "url": "https://opfab.github.io/whatisopfab/",
      "label": "first-menu-entry"
    },
    {
      "id": "uid test 0",
      "url": "https://www.lfenergy.org/",
      "label": "b-menu-entry"
    },
    {
      "id": "uid test 1",
      "url": "https://github.com/opfab/opfab.github.io",
      "label": "the-other-menu-entry"
    }
  ],
  "id":"BUNDLE_TEST"
  "name": "BUNDLE_TEST",
  "version": "1",
  "i18nLabelKey": "businessconfig-name-in-menu-bar",
  "menuEntries": [
    {
      "id": "uid test 0",
      "url": "https://opfab.github.io/whatisopfab/",
      "label": "first-menu-entry"
    },
    {
      "id": "uid test 0",
      "url": "https://www.lfenergy.org/",
      "label": "b-menu-entry"
    },
    {
      "id": "uid test 1",
      "url": "https://github.com/opfab/opfab.github.io",
      "label": "the-other-menu-entry"
    }
  ],
  "states" : {
          "start" : {
            "details" : [ {
              "title" : {
                "key" : "start.first.title"
              },
              "titleStyle" : "startTitle text-danger",
              "templateName" : "template1"
            } ]

          },
          "end" : {
            "details" : [ {
              "title" : {
                "key" : "end.first.title"
              },
              "titleStyle" : "startTitle text-info",
              "templateName" : "template2",
              "styles" : [ "bundleTest.css" ]
            } ]
          }
      }
}

Otherwise please refer to the Troubleshooting section to resolve the problem.

3.2. Templates

Templates are Handlebars template files. Templates are fuelled with a scope structure composed of

  • a card property (See card data model for more information)

  • a userContext :

    • login: user login

    • token: user jwt token

    • firstName: user first name

    • lastName: user last name

In addition to Handlebars basic syntax and helpers, OperatorFabric defines the following helpers :

3.2.1. OperatorFabric specific handlebars helpers

3.2.1.1. numberFormat

formats a number parameter using developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Nu mberFormat[Intl.NumberFormat]. The locale used is the current user selected one, and options are passed as hash parameters (see Handlebars doc Literals section).

{{numberFormat card.data.price style="currency" currency="EUR"}}
3.2.1.2. dateFormat

formats the submitted parameters (millisecond since epoch) using mement.format. The locale used is the current user selected one, the format is "format" hash parameter (see Handlebars doc Literals section).

{{dateFormat card.data.birthday format="MMMM Do YYYY, h:mm:ss a"}}
3.2.1.3. slice

extracts a sub array from ann array

example:

<!--
{"array": ["foo","bar","baz"]}
-->
<ul>
{{#each (slice array 0 2)}}
  <li>{{this}}</li>
{{/each}}
</ul>

outputs:

<ul>
  <li>foo</li>
  <li>bar</li>
</ul>

and

<!--
{"array": ["foo","bar","baz"]}
-->
<ul>
{{#each (slice array 1)}}
  <li>{{this}}</li>
{{/each}}
</ul>

outputs:

<ul>
  <li>bar</li>
  <li>baz</li>
</ul>
3.2.1.4. now

outputs the current date in millisecond from epoch. The date is computed from application internal time service and thus may be different from the date that one can compute from javascript api which relies on the browsers' system time.

NB: Due to Handlebars limitation you must provide at least one argument to helpers otherwise, Handlebars will confuse a helper and a variable. In the bellow example, we simply pass an empty string.

example:

<div>{{now ""}}</div>
<br>
<div>{{dateFormat (now "") format="MMMM Do YYYY, h:mm:ss a"}}</div>

outputs

<div>1551454795179</div>
<br>
<div>mars 1er 2019, 4:39:55 pm</div>

for a local set to FR_fr

3.2.1.5. preserveSpace

preserves space in parameter string to avoid html standard space trimming.

{{preserveSpace card.data.businessId}}
3.2.1.6. bool

returns a boolean result value on an arithmetical operation (including object equality) or boolean operation.

Arguments: - v1: left value operand - op: operator (string value) - v2: right value operand

arithmetical operators:

  • ==

  • ===

  • !=

  • !==

  • <

  • >

  • >=

boolean operators:

  • &&

  • ||

examples:

{{#if (bool v1 '<' v2)}}
  v1 is strictly lower than v2
{{else}}
 V2 is lower or equal to v1
{{/if}}
3.2.1.7. math

returns the result of a mathematical operation.

arguments:

  • v1: left value operand

  • op: operator (string value)

  • v2: right value operand

arithmetical operators:

  • +

  • -

  • *

  • /

  • %

example:

{{math 1 '+' 2}}
3.2.1.8. split

splits a string into an array based on a split string.

example:

<ul>
{{#each (split 'my.example.string' '.')}}
  <li>{{this}}</li>
{{/each}}
</ul>

outputs

<ul>
  <li>my</li>
  <li>example</li>
  <li>string</li>
</ul>
3.2.1.9. svg

outputs a svg tag with lazy loading, and missing image replacement message. The image url is the concatenation of an arbitrary number of helper arguments

{{{svg baseUri scheduledOpId "/" substation "/before/"
computationPhaseOrdinal}}}
3.2.1.10. i18n

outputs a i18n result from a key and some parameters. There are two ways of configuration :

  • Pass an object as sole argument. The object must contain a key field (string) and an optional parameter field (map of parameterKey ⇒ value)

    {{i18n card.data.i18nTitle}}
  • Pass a string key as sole argument and use hash parameters (see Handlebars doc Literals section) for i18n string parameters.

<!--
emergency.title=Emergency situation happened on {{date}}. Cause : {{cause}}.
-->
{{i18n "emergency.title" date="2018-06-14" cause="Broken Coffee Machine"}}

outputs

Emergency situation happened on 2018-06-14. Cause : Broken Cofee Machine
3.2.1.11. sort

sorts an array or some object’s properties (first argument) using an optional field name (second argument) to sort the collection on this fields natural order.

If there is no field argument provided :

  • for an array, the original order of the array is kept ;

  • for an object, the structure is sorted by the object field name.

<!--
users :

{"john": { "firstName": "John", "lastName": "Cleese"},
"graham": { "firstName": "Graham", "lastName": "Chapman"},
"terry": { "firstName": "Terry", "lastName": "Gilliam"},
"eric": { "firstName": "Eric", "lastName": "Idle"},
"terry": { "firstName": "Terry", "lastName": "Jones"},
"michael": { "firstName": "Michael", "lastName": "Palin"},
-->

<ul>
{{#each (sort users)}}
    <li>{{this.firstName}} {{this.lastName}}</li>
{{/each}}
</ul>

outputs :

<ul>
  <li>Eric Idle</li>
  <li>Graham Chapman</li>
  <li>John Cleese</li>
  <li>Michael Pallin</li>
  <li>Terry Gilliam</li>
  <li>Terry Jones</li>
</ul>

and

<ul>
{{#each (sort users "lastName")}}
    <li>{{this.firstName}} {{this.lastName</li>
{{/each}}
</ul>

outputs :

<ul>
  <li>Graham Chapman</li>
  <li>John Cleese</li>
  <li>Terry Gilliam</li>
  <li>Eric Idle</li>
  <li>Terry Jones</li>
  <li>Michael Pallin</li>
</ul>
3.2.1.12. arrayContains

Verify if an array contains a specified element. If the array does contain the element, it returns true. Otherwise, it returns false.

<p {{#if (arrayContains colors 'red')}}class="text-danger"{{/if}}>test</p>

If the colors array contains 'red', the output is:

<p class="text-danger">test</p>
3.2.1.13. times

Allows to perform the same action a certain number of times. Internally, this uses a for loop.

{{#times 3}}
  <p>test</p>
{{/times}}

outputs :

<p>test</p>
<p>test</p>
<p>test</p>
3.2.1.14. toBreakage

Change the breakage of a string. The arguments that you can specify are:

  • lowercase ⇒ The string will be lowercased

  • uppercase ⇒ The string will be uppercased

{{toBreakage key 'lowercase'}}s

If the value of the key variable is "TEST", the output will be:

tests
3.2.1.15. keyValue

This allows to traverse a map.

Notice that this should normally be feasible by using the built-in each helper, but a client was having some troubles using it so we added this custom helper.

{{#keyValue studentGrades}}
  <p>{{key}}: {{value}}</p>
{{/keyValue}}

If the value of the studentGrades map is:

{
  'student1': 15,
  'student2': 12,
  'student3': 9
}

The output will be:

<p>student1: 15</p>
<p>student2: 12</p>
<p>student3: 9</p>
3.2.1.16. keepSpacesAndEndOfLine

Convert a string to a light HTML by replacing :

  • each new line character with <br/>

  • spaces with &nbsp; when there is at least two consecutive spaces.

3.2.2. Charts

The library charts.js is integrate in operator fabric, it means it’s possible to show charts in cards, you can find a bundle example in the operator fabric git (src/test/utils/karate/businessconfig/resources/bundle_test_api).

4. Response cards

Within your template, you can allow the user to perform some action (respond to a form, answer a question, …​). The user fills these information and then clicks on a submit button. When he submits this action, a new card is created and emitted to a third-party tool.

This card is called "a child card" as it is attached to the card where the question came from : "the parent card". This child card is also send to the users that have received the parent card. From the ui point of view, the information of the child cards can be integrated in real time in the parent card if configured.

The process can be represented as follow :

ResponseCardSequence

Notice that the response will be associated to the entity and not to the user, i.e the user responds on behalf of his entity. A user can respond more than one time to a card (a future evolution could add the possibility to limit to one response per entity).

You can view a screenshot of an example of card with responses :

ResponseCardScreenshot2

4.1. Steps needed to use a response card

4.1.1. Define a third party tool

The response card is to be received by a third party application for business processing. The third-party application will receive the card as an HTTP POST request. The card is in json format (the same format as when we send a card). The field data in the json contains the user response.

The url of the third party receiving the response card is to be set in the .yml of the publication service. Here is an example with two third parties configured.

externalRecipients-url: "{\
           third-party1: \"http://thirdparty1/test1\", \
           third-party2: \"http://thirdparty2:8090/test2\", \
           }"

The name to use for the third-party is the publisherId of the parent card.

For the url, do not use localhost if you run OperatorFabric in a docker, as the publication-service will not be able to join your third party.

4.1.2. Configure the response in config.json

A card can have a response only if it’s in a process/state that is configured for. To do that you need to setup the good configuration in the config.json of the concerned process. Here is an example of configuration :

{
  "id": "defaultProcess",
  "name": "Test",
  "version": "1",
  "states": {
    "questionState": {
      "name": "question.title",
      "color": "#8bcdcd",
      "response": {
        "state": "responseState",
        "externalRecipients":["externalRecipient1", "externalRecipient2"],
        "btnColor": "GREEN",
        "btnText": {
          "key": "question.button.text"
        }
      },
      "details": [
        {
          "title": {
            "key": "question.title"
          },
          "templateName": "question",
          "styles": [
            "style"
          ]
        }
      ],
      "acknowledgementAllowed": false
    }
  }
}

We define here a state name "questionState" with a response field. Now, if we send a card with process "defaultProcess" and state "questionState" , the user will have the possibility to respond if he has the good privileges.

  • The field "state" in the response field is used to define the state to use for the response (the child card).

  • The field "externalRecipients" define the recipients of the response card. These recipients are keys referenced in the config file of cards-publication service, in "externalRecipients-url" element. This field is optional.

  • The field "btnColor" define the color of the submit button for the response, it is optional and there is 3 possibilities : RED , GREEN , YELLOW

  • The field "btnText"is the i18n key of the title of the submit button, it is optional.

4.1.3. Design the question form in the template

For the user to response you need to define the response form in the template with standard HTML syntax

To enable operator fabric to send the response, you need to implement a javascript function in your template called templateGateway.validyForm which return an object containing three fields :

  • valid (boolean) : true if the user input is valid

  • errorMsg (string) : message in case of invalid user input. If valid is true this field is not necessary.

  • formData (any) : the user input to send in the data field of the child card. If valid is false this field is not necessary.

This method will be called by OperatorFabric when user click on the button to send the response

You can find an example in the file src/test/utils/karate/businessconfig/resources/bundle_defaultProcess/template/en/question.handlebars.

4.1.4. Define permissions

To respond to a card a user must have the right privileges, it is done using "perimeters". The user must be in a group that is attached to a perimeter with a right "Write" for the concerned process/state, the state being the response state defined in the config.json.

Here is an example of definition of a perimeter :

{
  "id" : "perimeterQuestion",
  "process" : "defaultProcess",
  "stateRights" : [
    {
      "state" : "responseState",
      "right" : "Write"
    }
  ]
}

To configure it in OperatorFabric , you need to make a POST of this json file to the end point /users/perimeters.

To add it to a group name for example "mygroup", you need to make a PATCH request to end point 'users/groups/mygroup/perimeters' with payload ["perimeterQuestion"]

If you don’t want OperatorFabric to check for user perimeter when responding to a card, you can add the variable "checkPerimeterForResponseCard" and set it to false, in the config file of cards-publication and in web-ui.json.

4.2. Send a question card

The question card is like a usual card except that you have the field "entitiesAllowedToRespond" to set with the entities allowed to respond to the card. If the user is not in the entity, he will not be able to respond.

...
"process"  :"defaultProcess",
"processInstanceId" : "process4",
"state": "questionState",
"entitiesAllowedToRespond": ["ENTITY1","ENTITY2"],
"severity" : "ACTION",
...

4.3. Integrate child cards

For each user response, a child card containing the response is emitted and stored in OperatorFabric like a normal card. It is not directly visible on the ui but this child card can be integrated in real time to the parent card of all the users watching the card. To do that, you need some code in the template to process child data:

  • You can access child cards via the javascript method templateGateway.childCards() which returns an array of the child cards. The structure of a child card is the same as the structure of a classic card.

  • As child cards are arriving in real time, you need to define a method call templateGateway.applyChildCards() which will be called by OperatorFabric each time the list of child cards is evolving.

  • To integrate the child cards when loading the card you need to call to templateGateway.applyChildCards(). (OperatorFabric is not calling the method on card loading)

You can find an example in the file src/test/utils/karate/businessconfig/resources/bundle_defaultProcess/template/en/question.handlebars.

5. Archived Cards

5.1. Key concepts

Every time a card is published, in addition to being delivered to the users and persisted as a "current" card in MongoDB, it is also immediately persisted in the archived cards.

Archived cards are similar in structure to current cards, but they are managed differently. Current cards are uniquely identified by their id (made up of the publisher and the process id). That is because if a new card is published with id as an existing card, it will replace it in the card collection. This way, the current card reflects the current state of a process instance. In the archived cards collection however, both cards will be kept, so that the archived cards show all the states that a given process instance went through.

5.2. Archives screen in the UI

The Archives screen in the UI allows the users to query these archives with different filters. The layout of this screen is very similar to the Feed screen: the results are displayed in a (paginated) card list, and the user can display the associated card details by clicking a card in the list.

The results of these queries are limited to cards that the user is allowed to see, either :

  • because this user is direct recipient of the card,

  • because he belongs to a group (or entity) that is a recipient,

  • or because he belongs to a group that has the right to receive the card (via definition of perimeters)

If a card is sent to an entity and a group, then this user must be part of both the group and the entity.

6. User management

The User service manages users, groups, entities and perimeters (linked to groups).

Users

represent account information for a person destined to receive cards in the OperatorFabric instance.

Groups
  • represent set of users destined to receive collectively some cards.

  • has a set of perimeters to define rights for card reception in OperatorFabric.

Entities
  • represent set of users destined to receive collectively some cards.

  • can be used in a way to handle rights on card reception in OperatorFabric.

The user define here is an internal representation of the individual card recipient in OperatorFabric the authentication is leave to specific OAuth2 external service.
In the following commands the $token is an authentication token currently valid for the OAuth2 service used by the current OperatorFabric system.

6.1. Users, groups, entities and perimeters

User service manages users, groups, entities and perimeters.

6.1.1. Users

Users are the individuals and mainly physical person who can log in OperatorFabric.

The access to this service has to be authorized, in the OAuth2 service used by the current OperatorFabric instance, at least to access User information and to manage Users. The membership of groups and entities are stored in the user information.

6.1.1.1. Automated user creation

In case of a user does exist in a provided authentication service but he does not exist in the OperatorFabric instance, when he is authenticated and connected for the first time in the OperatorFabric instance, the user is automatically created in the system without attached group or entity. The administration of the groups, entities and perimeters is dealt by the administrator manually. More details about automated user creation here .

6.1.2. Groups

The notion of group is loose and can be used to simulate role in OperatorFabric. Groups are used to send cards to several users without a name specifically. The information about membership to a group is stored in the user information. A group contains a list of perimeters which define the rights of reception/writing for a couple process/state. The rules used to send cards are described in the recipients section .

6.1.3. Entities

Entities are used to send cards to several users without a name specifically. The information about membership to an entity is stored in the user information. Examples using entities can be found here .

6.1.4. Perimeters

Perimeters are used to define rights for reading/writing cards. A perimeter is composed of an identifier (unique), a process name and a list of state/rights couple. Possible rights for receiving/writing cards are :

  • Receive : the rights for receiving a card

  • Write : the rights for writing a card, that is to say respond to a card or create a new card

  • ReceiveAndWrite : the rights for receiving and writing a card

6.1.5. Alternative way to manage groups and entities

The standard way to handle groups and entities in OperatorFabric instance is dealt on the user information. There is an alternative way to manage groups and entities through the authentication token, the groups and entities are defined by the administrator of the authentication service. See here for more details to use this feature.

7. UI Customization

7.1. UI configuration

The web-ui.json file permit to configure parameters for customization , the list of parameters is described here

It is possible to declare specific business menu in the upper part of OperatorFabric. Those elements are declared in the config.json file of the bundles.

If there are several items to declare for a businessconfig service, a title for the businessconfig menu section need to be declared within the i18nLabelKey attribute, otherwise the first and only menu entry item is used to create an entry in the menu nav bar of OperatorFabric.

7.2.1. config.json declaration

This kind of objects contains the following attributes :

  • id: identifier of the entry menu in the UI;

  • url: url opening a new page in a tab in the browser;

  • label: it’s an i18n key used to l10n the entry in the UI.

  • linkType: Defines how business menu links are displayed in the navigation bar and how they open. Possible values:

    • TAB: Only a text link is displayed, and clicking it opens the link in a new tab.

    • IFRAME: Only a text link is displayed, and clicking it opens the link in an iframe in the main content zone below the navigation bar.

    • BOTH: Both a text link and a little arrow icon are displayed. Clicking the text link opens the link in an iframe while clicking the icon opens in a new tab. This is also the default value.

7.2.2. Examples

In the following examples, only the part relative to menu entries in the config.json file is detailed, the other parts are omitted and represented with a '…'.

Single menu entry

{
	…
	"menuEntries":[{
			"id": "identifer-single-menu-entry",
			"url": "https://opfab.github.io",
			"label": "single-menu-entry-i18n-key",
			"linkType": "BOTH"
		}],
}

Several menu entries

Here a sample with 3 menu entries.

{
	…
	"i18nLabelKey":"businessconfig-name-in-menu-navbar",
	"menuEntries": [{
			"id": "firstEntryIdentifier",
			"url": "https://opfab.github.io/whatisopfab/",
			"label": "first-menu-entry",
			"linkType": "BOTH"
		},
		{
			"id": "secondEntryIdentifier",
			"url": "https://www.lfenergy.org/",
			"label": "second-menu-entry"
		} ,
		{
			"id": "businessconfigEntryIdentifier",
			"url": "https://opfab.github.io",
			"label": "businessconfig-menu-entry"
		}]
}

7.2.3. Operator Fabric theme

If the page opened via the menu is in a iframe, the current theme of operator fabric will be passed as a parameter in the request send to the web site. The parameter is named opfab_theme and has the value of the current theme : DAY, NIGHT or LEGACY.

Example :

 http://mysite.com/index.htm?opfab_theme=NIGHT

When user is switching theme and the iframe is opened, the iframe will be reloaded.