Force journal entry on alarm menu actions

Prerequisites

Modules

Checkout branches

If you don’t have the prerequisites modules installed :

git checkout origin/osp-alarms-web-configuration .

Branches listed above assume you already have a working stack (with all core modules installed), if this is not the case you should first deploy default configuration :

git checkout origin/osp-default-configuration .

Description

Alarms menu actions allow interactions with alarms from the user interface (especially from an alarm table or alarm history table widget). Default actions allow for example to change an alarm severity or to acknowledge it.

../../_images/force-journal-entry-default-alarms-menu.png

We’ve seen how to create dynamic action in menu, now let’s see how to force journal entry upon actions.

First, let’s define what our implementation should do :

  • Force user to enter a journal entry upon some alarm actions (for this example, we’ll use the Acknowledge action)

    • Should be the case if a single alarm is clicked

    • Should be the case if multiple alarms are selected (obviously, journal entry should be requested only once and attached to all selected alarms)

Steps

First, we need an alarm table. We have “default provided” dashboard in root, defined by root/dashboard.view and root/dashboard.web on which we are going to add a simple alarm table so that root/dashboard.view will look like this :

{
    "configuration": [
      {
        "alarmWidgetSettings": {
            "defaultFilter": "root.alarms.filters.all",
            "defaultView": "root.alarms.views.default"
        },
        "menuReferences": ["root.alarms.menus"],
        "id": "06xPX-BM",
        "type": "AlarmTable",
        "title": ""
      }
    ],
    "layout": {
      "lg": [
        {
          "w": 12,
          "h": 5,
          "x": 0,
          "y": 0,
          "i": "06xPX-BM"
        }
      ]
    },
    "breakpoints": {
      "lg": 1200,
      "md": 996,
      "sm": 768,
      "xs": 480,
      "xxs": 0
    },
    "cols": {
      "lg": 12,
      "md": 10,
      "sm": 6,
      "xs": 4,
      "xxs": 2
    },
    "rowHeight": 150
  }

Notice emphasized lines, linking already defined bits (default provided filter, view and menu) to our newly added alarm table.

Let’s now have a look at the default provided alarm menu (root/alarms/menus/menu.web), where Acknowledge action is configured as such :

        {
            "label": "Acknowledge",
            "icon": "thumb_up",
            "context": [
                {
                    "type": "AlarmTable",
                    "action": "root.alarms.actions.acknowledge",
                    "condition": "${alarms.selected}.length > 0",
                    "input": {
                        "alarmsIds": {
                            "extract": "alarms.selected[*]._id"
                        }
                    }
                },
                {
                    "type": "AlarmTable",
                    "action": "root.alarms.actions.acknowledge",
                    "condition": "${alarms.selected}.length === 0",
                    "input": {
                        "alarmsIds": {
                            "extract": "alarms.clicked._id"
                        }
                    }
                }
            ]
        },

We can see two contexts, first one used when multiple alarms are selected ("condition": "${alarms.selected}.length > 0") and a second one for a single clicked alarm. Both of those contexts do the same thing, they call the action defined in root.alarms.actions.acknowledge which can take one or multiple alarms id as input. For the first context since we have multiple alarms selected then all of their ids must be fed to the action : "extract": "alarms.selected[*]._id". On the other hand, for the last context where a single alarm is clicked, its id can directly be retrieved : "extract": "alarms.clicked._id".

So, we have an action doing what we want (acknowledge one or more alarms) and a way to extract what it needs (one or more alarm id).

By inspecting how the root.alarms.actions.acknowledge is defined we can see :

{
    "moduleId": [
        "modules.web.web-1"
    ],
    "type": "ACKNOWLEDGE_ALARMS"
}

If you have the composer installed you can remove the ACKNOWLEDGE_ALARMS and use CTRL + space to see what other types of action exist (otherwise, they are listed in the documentation). Among the offered action types we have a way to add a journal entry (ADD_JOURNAL_ENTRY) and a way to acknowledge alarms (ACKNOWLEDGE_ALARMS) but nothing to add a journal entry THEN acknowledge the alarm(s). Thankfully, there’s an action type allowing to run a script (RUN_SCRIPT) from which we can do virtually anything we want.

To do so we will need the script module. If you don’t already have it installed :

git checkout origin/osp-scripts-configuration .

Then we need to create a new action allowing to run our script. In root/alarms/actions create the run_script directory then inside this new directory an action.ospp file with the following content :

{
    "moduleId": [
        "modules.web.web-1"
    ],
    "type": "RUN_SCRIPT"
}

As its name implies, this action will run a script. We are now going to create said script to execute which wil both force the user the enter a journal entry and will then acknowledge the alarm(s) whose ids it will take as input.

Let’s create a directory root/force-journal-entry (you can obviously create this directory wherever you’d like as long as it is under the root one) for this script.

We could either used a value script as well as a detached script. Since we do not need any return value from the script, we will use a detached script (detached.scripts) which we will create in our new directory root/force-journal-entry/detached.scripts with following content :

{
    "moduleId": "modules.scripts.scripts-1",
    "scheduledExecutions": [],
    "accessedValues": [],
    "sourceFile": "root/force-journal-entry/justificator.js"
}

A few points regarding its content :

  • moduleId reference one instance of the script module (in this case we only have this one)

  • scheduledExecutions is empty because we want the script to be trigger manually and not automatically

  • accessedValue is empty because our script won’t need to read any value to run

  • sourceFile reference the file containing the actual source code of our script which we still need to create

So let’s create our script (root/force-journal-entry/justificator.js) with following content which we’ll explain bits by bits :

const main = () => {

    log.info("Justificator ");
    
};

main();

This is basic JavaScript main definition with main() called at the end. The only specificity lies in how we log our message using log.info(...). This logging API is provided by OnSphere and will take into account the script module logging configuration defined in modules/scripts/scripts-1/module.scripts (if no loggingConfiguration is defined then default are used, module.scripts content explanation can be found here).

We can “attach” this script to the “Acknowledge” entry in the alarms menu (root/alarms/menus/menu.web) by replacing :

        {
            "label": "Acknowledge",
            "icon": "thumb_up",
            "context": [
                {
                    "type": "AlarmTable",
                    "action": "root.alarms.actions.acknowledge",
                    "condition": "${alarms.selected}.length > 0",
                    "input": {
                        "alarmsIds": {
                            "extract": "alarms.selected[*]._id"
                        }
                    }
                },
                {
                    "type": "AlarmTable",
                    "action": "root.alarms.actions.acknowledge",
                    "condition": "${alarms.selected}.length === 0",
                    "input": {
                        "alarmsIds": {
                            "extract": "alarms.clicked._id"
                        }
                    }
                }
            ]
        },

with :

 1        {
 2            "label": "Acknowledge",
 3            "icon": "thumb_up",
 4            "context": [
 5                {
 6                    "type": "AlarmTable",
 7                    "action": "root.alarms.actions.run_script",
 8                    "condition": "${alarms.selected}.length > 0",
 9                    "input": {
10                        "scriptId": {
11                            "expression": "'root.force-journal-entry'"
12                        },
13                        "alarmsIds": {
14                            "extract": "alarms.selected[*]._id"
15                        },
16                    }
17                },
18                {
19                    "type": "AlarmTable",
20                    "action": "root.alarms.actions.run_script",
21                    "condition": "${alarms.selected}.length === 0",
22                    "input": {
23                        "scriptId": {
24                            "expression": "'root.force-journal-entry'"
25                        },
26                        "alarmsIds": {
27                            "extract": "alarms.clicked._id"
28                        },
29                    }
30                }
31            ]
32        },

We have :

  1. Changed called action (lines 7 and 20)

  2. Added configuration to specify the script we want to call from our new action (lines 10-12 and 23-25)

Note

“,” at the end of line 15 and 28 should be discarded. They are only here because shown file is truncated.

At this point, what we have is a menu entry allowing to trigger a script and feed it with one or more alarm id (you can check script module logs while trying to acknowledge an alarm to confirm this if you want) but no acknowledgement nor any prompt requesting a journal entry.

To actually acknowledge our alarm(s) we will add two lines in our script :

const main = () => {

    log.info("Justificator ");
    let alarmsIds = trigger.parameters[0].alarmsIds;
    alarms.acknowledge(alarmsIds);
    
};

main();

First added line is used to retrieve alarm(s) id(s) provided when the menu entry was clicked (notice the end field we access alarmsIds which is the same name used in our menu).

Second added line actually request for alarms whose ids have been provided to be acknowledged.

We can trigger our script from a single clicked alarm or multiple ones selected and acknowledge them through said script. What we’d like now is a prompt requesting the user to enter a message to justify the action. To do that we need to add a message section to our menu entry :

 1        {
 2            "label": "Acknowledge",
 3            "icon": "thumb_up",
 4            "context": [
 5                {
 6                    "type": "AlarmTable",
 7                    "action": "root.alarms.actions.run_script",
 8                    "condition": "${alarms.selected}.length > 0",
 9                    "input": {
10                        "scriptId": {
11                            "expression": "'root.force-journal-entry'"
12                        },
13                        "alarmsIds": {
14                            "extract": "alarms.selected[*]._id"
15                        },
16                        "message": {
17                            "prompt": {
18                                "type": "INPUT",
19                                "labels": {
20                                    "title": "Confirmation",
21                                    "message": "Veuillez confirmer le quittancement",
22                                    "save": "Confirmer",
23                                    "cancel": "Annuler"
24                                }
25                            }
26                        },
27                    }
28                },
29                {
30                    "type": "AlarmTable",
31                    "action": "root.alarms.actions.run_script",
32                    "condition": "${alarms.selected}.length === 0",
33                    "input": {
34                        "scriptId": {
35                            "expression": "'root.force-journal-entry'"
36                        },
37                        "alarmsIds": {
38                            "extract": "alarms.clicked._id"
39                        },
40                        "message": {
41                            "prompt": {
42                                "type": "INPUT",
43                                "labels": {
44                                    "title": "Confirmation",
45                                    "message": "Veuillez confirmer le quittancement",
46                                    "save": "Confirmer",
47                                    "cancel": "Annuler"
48                                }
49                            }
50                        },
51                    }
52                }
53            ]
54        },

Note

“,” at the end of line 26 and 50 should be discarded. They are only here because shown file is truncated.

Furthermore, we’ll see later that the API allowing to manipulate alarms through scripts requires a user parameter when adding journal entry to alarm (to be able to pinpoint who did the action in addition to why) so we’ll add it too :

 1        {
 2            "label": "Acknowledge",
 3            "icon": "thumb_up",
 4            "context": [
 5                {
 6                    "type": "AlarmTable",
 7                    "action": "root.alarms.actions.run_script",
 8                    "condition": "${alarms.selected}.length > 0",
 9                    "input": {
10                        "scriptId": {
11                            "expression": "'root.force-journal-entry'"
12                        },
13                        "alarmsIds": {
14                            "extract": "alarms.selected[*]._id"
15                        },
16                        "message": {
17                            "prompt": {
18                                "type": "INPUT",
19                                "labels": {
20                                    "title": "Confirmation",
21                                    "message": "Veuillez confirmer le quittancement",
22                                    "save": "Confirmer",
23                                    "cancel": "Annuler"
24                                }
25                            }
26                        },
27                        "user": {
28                            "extract": "user",
29                            "as": "object"
30                        }
31                    }
32                },
33                {
34                    "type": "AlarmTable",
35                    "action": "root.alarms.actions.run_script",
36                    "condition": "${alarms.selected}.length === 0",
37                    "input": {
38                        "scriptId": {
39                            "expression": "'root.force-journal-entry'"
40                        },
41                        "alarmsIds": {
42                            "extract": "alarms.clicked._id"
43                        },
44                        "message": {
45                            "prompt": {
46                                "type": "INPUT",
47                                "labels": {
48                                    "title": "Confirmation",
49                                    "message": "Veuillez confirmer le quittancement",
50                                    "save": "Confirmer",
51                                    "cancel": "Annuler"
52                                }
53                            }
54                        },
55                        "user": {
56                            "extract": "user",
57                            "as": "object"
58                        }
59                    }
60                }
61            ]
62        },

To retrieve this message (as well as the user who entered it) and associate it to our clicked/selected alarm(s) we will update our script :

 1const main = () => {
 2
 3    log.info("Justificator ");
 4    let alarmsIds = trigger.parameters[0].alarmsIds;
 5    let userJournalEntry = trigger.parameters[0].message;
 6    let user = trigger.parameters[0].user.username;
 7    
 8    alarms.addJournal(alarmsIds, userJournalEntry, user);
 9    alarms.acknowledge(alarmsIds);
10    
11};
12
13main();

We can even go a little step further and add a feedback to the user so that it is clear the script reached its end without issues :

 1const main = () => {
 2
 3    log.info("Justificator ");
 4    let alarmsIds = trigger.parameters[0].alarmsIds;
 5    let userJournalEntry = trigger.parameters[0].message;
 6    let user = trigger.parameters[0].user.username;
 7    
 8    alarms.addJournal(alarmsIds, userJournalEntry, user);
 9    alarms.acknowledge(alarmsIds);
10    
11    execution.reportResult("Alarm(s) successfully acknowledged");
12    
13};
14
15main();