1. Prerequisites

To use OperatorFabric, you need a linux OS with the following:

  • Docker install with 4Gb of space

  • 16Gb of RAM

2. Install and run server

To start OperatorFabric, you first need to clone the getting started git

git clone https://github.com/opfab/operatorfabric-getting-started.git

Launch the startserver.sh in the server directory. You need to wait for all the services to start (it usually takes one minute to start), it is done when the console prompt is available again.

Test the connection to the UI: to connect to OperatorFabric, open in a browser the following page: localhost:2002/ui/ and use operator1_fr as login and test as password.

After connection, you should see the following screen

empty opfab screenshot

To stop the server (if you start it in background), use:

docker-compose down &

3. Examples

For each example, useful files and scripts are in the directory client/exampleX.

All examples assume you connect to the server from localhost (otherwise change the provided scripts)

3.1. Example 1: Send and update a basic card

Go in directory client/example1.

To receive the test cards it is necessary to configure a perimeter as you will see in details in Example 5.

Configure the required perimeter by executing the provided script:

./setupPerimeter.sh perimeter.json

Send a card, using the provided script :

./sendCard.sh card.json

The result should be a 201 Http status.

See the result in the UI, you should see a card, if you click on it you’ll see the detail

detail card screenshot

3.1.1. Anatomy of the card :

A card is containing information regarding the publisher, the recipients, the process, the data to show…​

More information can be found in the Card Structure section of the reference documentation.

{
        "publisher" : "message-publisher",
        "processVersion" : "1",
        "process" :"defaultProcess",
        "processInstanceId" : "hello-world-1",
        "state" : "messageState",
        "groupRecipients": ["Dispatcher"],
        "severity" : "INFORMATION",
        "startDate" : 1553186770681,
        "summary" : {"key" : "defaultProcess.summary"},
        "title" : {"key" : "defaultProcess.title"},
        "data" : {"message" :"Hello World !!! That's my first message"}
}
If you open the json file of the card, you will see '${current_date_in_milliseconds_from_epoch}' for the field 'startDate'. We have used this so that the date of the card is the current day (or the next day in some other examples). Indeed, in the shell script that sends the card, you will see that we create an environment variable with the current date which is then injected into the json file.

3.1.2. Update the card

We can send a new version of the card (updateCard.json):

  • change the message, field data.message in the JSON File

  • the severity , field severity in the JSON File

{
        "publisher" : "message-publisher",
        "processVersion" : "1",
        "process"  :"defaultProcess",
        "processInstanceId" : "hello-world-1",
        "state" : "messageState",
        "groupRecipients": ["Dispatcher"],
        "severity" : "ALARM",
        "startDate" : 1553186770681,
        "summary" : {"key" : "defaultProcess.summary"},
        "title" : {"key" : "defaultProcess.title"},
        "data" : {"message" :":That's my second message"}
}

You can send the updated card with:

./sendCard.sh cardUpdate.json

The card should be updated on the UI.

3.1.3. Delete the card

You can delete the card using DELETE HTTP code with reference to publisher and processInstanceId

curl -s -X DELETE http://localhost:2102/cards/defaultProcess.hello-world-1

or use provided script:

./deleteCard.sh

3.2. Example 2: Publish a new bundle

The way the card is display in the UI is defined via a Bundle containing templates and process description.

The bundle structure is the following:

├── css : stylesheets files
└── template : handlebar templates for detail card rendering
config.json : process description and global configuration
i18n.json : internalization file

The bundle is provided in the bundle directory of example2. It contains a new version of the bundle used in example1.

We just change the template and the stylesheet instead of displaying:

Message :  The message

we display:

You received the following message

The message

If you look at the template file (template/template.handlebars):

<h2> You received the following message </h2>

{{card.data.message}}

In the stylesheet css/style.css we just change the color value to red (#ff0000):

h2{
        color:#ff0000;
        font-weight: bold;
}

The global configuration is defined in config.json :

{
        "id":"defaultProcess",
        "version":"2",
        "states":{
                "messageState" : {
                        "templateName" : "template",
                        "styles" : [ "style" ]
                }
        }
}

To keep the old bundle, we create a new version by setting version to 2.

3.2.1. Package your bundle

Your bundle need to be package in a tar.gz file, a script is available

./packageBundle.sh

A file name bundle.tar.gz will be created.

3.2.2. Get a Token

To send the bundle you need to be authenticated. To get a token you can source the provided script:

source ../getToken.sh

This will run the following command:

curl -s -X POST -d "username=admin&password=test&grant_type=password&client_id=opfab-client" http://localhost:2002/auth/token

This should return a JSON a response like this:

{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJSbXFOVTNLN0x4ck5SRmtIVTJxcTZZcTEya1RDaXNtRkw5U2NwbkNPeDBjIn0.eyJqdGkiOiIzZDhlODY3MS1jMDhjLTQ3NDktOTQyOC1hZTdhOTE5OWRmNjIiLCJleHAiOjE1NzU1ODQ0NTYsIm5iZiI6MCwiaWF0IjoxNTc1NTQ4NDU2LCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODA4MC9hdXRoL3JlYWxtcy9kZXYiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiYTNhM2IxYTYtMWVlYi00NDI5LWE2OGItNWQ1YWI1YjNhMTI5IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoib3BmYWItY2xpZW50IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiODc3NzZjOTktYjA1MC00NmQxLTg5YjYtNDljYzIxNTQyMDBhIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic3ViIjoiYWRtaW4iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIn0.XMLjdOJV-A-iZrtq7sobcvU9XtJVmKKv9Tnv921PjtvJ85CnHP-qXp2hYf5D8TXnn32lILVD3g8F9iXs0otMAbpA9j9Re2QPadwRnGNLIzmD5pLzjJ7c18PWZUVscbaqdP5PfVFA67-j-YmQBwxiys8psF8keJFvmg-ExOGh66lCayClceQaUUdxpeuKFDxOSkFVEJcVxdelFtrEbpoq0KNPtYk7vtoG74zO3KjNGrzLkSE_e4wR6MHVFrZVJwG9cEPd_dLGS-GmkYjB6lorXPyJJ9WYvig56CKDaFry3Vn8AjX_SFSgTB28WkWHYZknTwm9EKeRCsBQlU6MLe4Sng","expires_in":36000,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzZjdkZTM0OC05N2Q5LTRiOTUtYjViNi04MjExYTI3YjdlNzYifQ.eyJqdGkiOiJhZDY4ODQ4NS1hZGE0LTQwNWEtYjQ4MS1hNmNkMTM2YWY0YWYiLCJleHAiOjE1NzU1NTAyNTYsIm5iZiI6MCwiaWF0IjoxNTc1NTQ4NDU2LCJpc3MiOiJodHRwOi8va2V5Y2xvYWs6ODA4MC9hdXRoL3JlYWxtcy9kZXYiLCJhdWQiOiJodHRwOi8va2V5Y2xvYWs6ODA4MC9hdXRoL3JlYWxtcy9kZXYiLCJzdWIiOiJhM2EzYjFhNi0xZWViLTQ0MjktYTY4Yi01ZDVhYjViM2ExMjkiLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoib3BmYWItY2xpZW50IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiODc3NzZjOTktYjA1MC00NmQxLTg5YjYtNDljYzIxNTQyMDBhIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUifQ.sHskPtatqlU9Z8Sfq6yvzUP_L6y-Rv26oPpykyPgzmk","token_type":"bearer","not-before-policy":0,"session_state":"87776c99-b050-46d1-89b6-49cc2154200a","scope":"email profile"}

Your token is the access_token value in the JSON, which the script will export to a $token environment variable.

The sendBundle.sh script below will use of this variable.

The token will be valid for 10 hours, after you will need to ask for a new one.

3.2.3. Send the bundle

Executing the sendBundle.sh script will send the bundle.

You can now execute the script, it will send the bundle.

./sendBundle.sh

You should received the following JSON in response, describing your bundle.

{"id":"defaultProcess","name":"process.name","version":"2","states":{"messageState":{"responseData":null,"acknowledgmentAllowed":"Always","color":null,"name":null,"description":null,"showDetailCardHeader":null,"userCard":null,"templateName":"template","styles":["style"],"type":null,"response":null}},"uiVisibility":null}

3.2.4. Send a card

You can send the following card to test your new bundle:

{
        "publisher" : "message-publisher",
        "processVersion" : "2",
        "process"  :"defaultProcess",
        "processInstanceId" : "hello-world-1",
        "state": "messageState",
        "groupRecipients": ["Dispatcher"],
        "severity" : "INFORMATION",
        "startDate" : 1553186770681,
        "summary" : {"key" : "defaultProcess.summary"},
        "title" : {"key" : "defaultProcess.title"},
        "data" : {"message":"Hello world in new version"}
}

To use the new bundle, we set processVersion to "2"

To send the card:

./sendCard.sh

You should see in the UI the detail card with the new template.

3.3. Example 3: Process with state

For this example, we will set the following process:

  • Step 1: A critical situation arises on the High Voltage grid

  • Step 2: The critical situation evolve

  • Step 3: The critical situation ends

To model this process in OperatorFabric, we will use a "Process" with "States", we will model this in the config.json of the bundle:

{
        "id":"criticalSituation",
        "name": "Critical situation process",
        "version":"1",
        "states":{
                "criticalSituation-begin" : {
                        "templateName" : "criticalSituationTemplate",
                        "styles" : [ "style" ],
                        "acknowledgmentAllowed": "Always"
                },
                "criticalSituation-update" : {
                        "templateName" : "criticalSituationTemplate",
                        "styles" : [ "style" ],
                        "acknowledgmentAllowed": "Always"
                },
                "criticalSituation-end" : {
                        "templateName" : "endCriticalSituationTemplate",
                        "styles" : [ "style" ],
                        "acknowledgmentAllowed": "Always"
                }
        }
}

You can see in the JSON we define a process name "criticalSituation" with 3 states: criticalSituation-begin, criticalSituation-update and criticalSituation-end. For each state we define a title for the card, and the template a stylesheets to use.

The title is a key which refer to i18n.json file:

{
        "criticalSituation-begin":{
                "title":"CRITICAL SITUATION",
                "summary":" CRITICAL SITUATION ON THE GRID , SEE DETAIL FOR INSTRUCTION"
        },
        "criticalSituation-update":{
                "title":"CRITICAL SITUATION - UPDATE",
                "summary":" CRITICAL SITUATION ON THE GRID , SEE DETAIL FOR INSTRUCTION"
        },
        "criticalSituation-end":{
                "title":"CRITICAL SITUATION - END",
                "summary":" CRITICAL SITUATION ENDED"
        }

}

The templates can be found in the template directory.

Before sending cards it is necessary to configure the required perimeter by executing the provided script:

./setupPerimeter.sh perimeter.json

We can now send cards and simulate the process, first we send a card at the beginning of the critical situation:

{
        "publisher" : "alert-publisher",
        "processVersion" : "1",
        "process"  :"criticalSituation",
        "processInstanceId" : "alert1",
        "state": "criticalSituation-begin",
        "groupRecipients": ["Dispatcher"],
        "severity" : "ALARM",
        "startDate" : 1553186770681,
        "summary" : {"key" : "criticalSituation-begin.summary"},
        "title" : {"key" : "criticalSituation-begin.title"},
        "data" : {"instruction":"Critical situation on the grid : stop immediatly all maintenance on the grid"}
}

The card refers to the process "criticalSituation" as defined in the config.json, the state attribute is put to "criticalSituation-begin" which is the first step of the process, again as defined in the config.json. The card can be sent via provided script :

./sendCard.sh card.json

Two other card have be provided to continue the process

  • cardUpdate.json: the state is criticalSituation-update

  • cardEnd.json: the state is criticalSituation-end and severity set to "compliant"

You can send these cards:

./sendCard.sh cardUpdate.json
./sendCard.sh cardEnd.json

3.4. Example 4: Time Line

To view the card in the time line, you need to set times in the card using timeSpans attributes as in the following card:

 {
        "publisher" : "scheduledMaintenance-publisher",
        "processVersion" : "1",
        "process"  :"maintenanceProcess",
        "processInstanceId" : "maintenance-1",
        "state": "planned",
        "groupRecipients": ["Dispatcher"],
        "severity" : "INFORMATION",
        "startDate" : 1553186770681,
        "summary" : {"key" : "maintenanceProcess.summary"},
        "title" : {"key" : "maintenanceProcess.title"},
        "data" : {
                "operationDescription":"Maintenance operation on the International France England (IFA) High Voltage line ",
                "operationResponsible":"RTE",
                "contactPoint":"By Phone : +33 1 23 45 67 89 ",
                "operationStartingTime":"Wed 11 dec 2019 8pm",
                "operationEndTime":"Thu 12 dec 2019 10am",
                "comment":"Operation has no impact on service"
                },
        "timeSpans" : [
        {"start" : 1576080876779},
        {"start" : 1576104912066}
            ]
}

For this example, we use a new publisher called "scheduledMaintenance-publisher". You won’t need to post the corresponding bundle to the businessconfig service as it has been loaded in advance to be available out of the box (only for the getting started). If you want to take a look at its content you can find it under server/businessconfig-storage/scheduledMaintenance-publisher/1.

Before sending the provided card provided, you need to set the good time values as epoch (ms) in the json. For each value you set, you will have a point in the timeline. In our example, the first point represent the beginning of the maintenance operation, and the second the end of the maintenance operation.

For example cards the dates are calculated automatically in the provided sendCard.sh script.

It is possible to change dates values by editing the card json file. To get the dates in Epoch, you can use the following commands:

For the first date:

date -d "+ 60 minutes" +%s%N | cut -b1-13

And for the second

date -d "+ 120 minutes" +%s%N | cut -b1-13

Before sending cards it is necessary to configure the required perimeter by executing the provided script:

./setupPerimeter.sh perimeter.json

To send the card use the provided script in example4 directory

./sendCard.sh card.json

A second card (card2.json) is provided as example, as before you can eventually change times values in the json file and then send it

./sendCard.sh card2.json

This time the severity of the card is ALERT, you should see the point in red in the timeline

example 4 screenshot

3.5. Example 5: Card routing mechanism

3.5.1. Card sent to a group

As we saw previously, if a card is sent to a group, then you need to be a member of the group and have the process / state of the card within the group’s perimeter to receive it.

3.5.2. Card sent to an entity

If a card is sent to an entity, then you must be a member of this entity and have the process / state of the card within the user’s perimeter. As the perimeters are attached to groups, the user must therefore be a member of a group attached to this perimeter.

Let’s send this card :

{
    "publisher" : "message-publisher",
    "processVersion" : "1",
    "entityRecipients" : ["ENTITY1_FR"],
    "process" :"defaultProcess",
    "processInstanceId" : "cardExample5",
    "state" : "messageState1",
    "severity" : "INFORMATION",
    "startDate" : 1553186770681,
    "summary" : {"key" : "defaultProcess.summary"},
    "title" : {"key" : "defaultProcess.title"},
    "data" : {"message" : "Hello World !!! Here is a message for ENTITY1_FR"}
}

Use the provided script :

./sendCard.sh cardSentToEntity.json

The result should be a 201 Http status.

See the result in the UI, you should not see the card.

To receive the card you need to create a perimeter and to do it you need to be authenticated. To get a token you can source the provided script:

source ../getToken.sh

Now let’s create this perimeter :

{
  "id" : "getting-startedPerimeter",
  "process" : "defaultProcess",
  "stateRights" : [
    {
      "state" : "messageState1",
      "right" : "Receive"
    }
  ]
}

You can use this command line :

curl -X POST http://localhost:2103/perimeters -H "Content-type:application/json" -H "Authorization:Bearer $token" --data @perimeter.json

or use the provided script :

./createPerimeter.sh perimeter.json

The result should be a 201 Http status, and a json object such as:

{"id":"getting-startedPerimeter","process":"defaultProcess","stateRights":[{"state":"messageState","right":"Receive"}]}

Now let’s attach this perimeter to the Dispatcher group. You can use this command line :

curl -X PUT http://localhost:2103/perimeters/getting-startedPerimeter/groups -H "Content-type:application/json" -H "Authorization:Bearer $token" --data "[\"Dispatcher\"]"

or use the provided script :

./putPerimeterForGroup.sh

The result should be a 200 Http status.

Now, if you refresh the UI or send again the card, you should see the card.

3.5.3. Card sent to a group and an entity

If a card is sent to a group and an entity, then to receive the card the user must both a member of this entity and a member of this group and have the process / state of the card within the group’s perimeter.

Let’s send this card (for ENTITY1_FR and Dispatcher group with process/state not in user’s perimeter) :

{
        "publisher" : "message-publisher",
        "processVersion" : "1",
        "entityRecipients" : ["ENTITY1_FR"],
        "process"  :"defaultProcess",
        "processInstanceId" : "cardExample5_1",
        "state": "messageState2",
        "groupRecipients": ["Dispatcher"],
        "severity" : "INFORMATION",
        "startDate" : 1553186770681,
        "summary" : {"key" : "defaultProcess.summary"},
        "title" : {"key" : "defaultProcess.title"},
        "data" : {"message" : "Hello World !!! Here is a message for ENTITY1_FR and group Dispatcher - process/state not in operator1_fr perimeter "}
}

Use the provided script :

./sendCard.sh cardSentToEntityAndGroup_1.json

The result should be a 201 Http status.

See the result in the UI, you should not see the card.

Now let’s send this card (for ENTITY1_FR and Dispatcher group with process/state in user’s perimeter) :

{
        "publisher" : "message-publisher",
        "processVersion" : "1",
        "entityRecipients" : ["ENTITY1_FR"],
        "process"  :"defaultProcess",
        "processInstanceId" : "cardExample5_2",
        "state": "messageState",
        "groupRecipients": ["Dispatcher"],
        "severity" : "INFORMATION",
        "startDate" : 1553186770681,
        "summary" : {"key" : "defaultProcess.summary"},
        "title" : {"key" : "defaultProcess.title"},
        "data" : {"message" : "Hello World !!! Here is a message for ENTITY1_FR and group Planner - process/state in operator1_fr perimeter "}
}

Use the provided script :

./sendCard.sh cardSentToEntityAndGroup_2.json

The result should be a 201 Http status.

See the result in the UI, you should see the card.

4. Troubleshooting

4.1. My bundle is not loaded

The server send a {"status":"BAD_REQUEST","message":"unable to open submitted file","errors":["Error detected parsing the header"]}, despite correct http headers.

The uploaded bundle is corrupted. Test your bundle in a terminal (Linux solution).

Example for a bundle archive named MyBundleToTest.tar.gz giving the mentioned error when uploaded :

tar -tzf MyBundleToTest.tar.gz >/dev/null
tar: This does not look like a tar archive
tar: Skipping to next header
tar: Exiting with failure status due to previous errors

4.1.1. Solution

Extract content if possible and compress it to a correct compressed archive format.

4.2. I can’t upload my bundle

The server responds with a message like the following: {"status":"BAD_REQUEST","message":"unable to open submitted file","errors":["Input is not in the .gz format"]}

The bundle has been compressed using an unmanaged format.

4.2.1. Format verification

4.2.1.1. Linux solution

Command line example to verify the format of a bundle archive named MyBundleToTest.tar.gz(which gives the mentioned error when uploaded):

 tar -tzf MyBundleToTest.tar.gz >/dev/null

which should return in such case the following messages:

gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error is not recoverable: exiting now

4.2.2. Solution

Use tar.gz format for the archive compression. Shell command is tar -czvf MyBundleToTest.tar.gz config.json i18n.json template/ css/ for a bundle containing templates and css files.

4.3. My bundle is rejected due to internal structure

The server sends {"status":"BAD_REQUEST","message":"Incorrect inner file structure","errors":["$OPERATOR_FABRIC_INSTANCE_PATH/d91ba68c-de6b-4635-a8e8-b58 fff77dfd2/config.json (Aucun fichier ou dossier de ce type)"]}

Where $OPERATOR_FABRIC_INSTANCE_PATH is the folder where businessconfig files are stored server side.

4.3.1. Reason

The internal file structure of your bundle is incorrect. config.json file and folders need to be at the first level.

4.3.2. Solution

Add or organize the files and folders of the bundle to fit the Businessconfig bundle requirements.

4.4. My template is not used.

It need to be declared in the config.json of the bundle.

4.4.1. Solution

Add or verify the name of the templates declared in the config.json file of the bundle.

4.5. My value is not displayed in the detail template

There are several possibilities:

  • the path of the data used in the template is incorrect;

  • number of pair of { and } must be 2 (example : {{card.data.myValue}})