We ended up using Mistic100’s Query Builder for the UI portion of this project. The result was something like this:
The left hand side has the criteria, and right hand side has the actions.
Since the production build of this is targeted at a specific sector, and gives private information away, I had to blur out the actual data. The good news is that it wouldn’t make sense in the context of this blog and you can repurpose this however you’d like.
Seeding the Selections
The beauty of Mistic’s Query Builder is in the flexibility. For instance I use two tables to populate the filters containing the data that result in the selections or data types that can be entered – one for the criteria and one for the actions.
Here are a few records from the criteria table:
Here are a few from the actions table:
Boolean Expressions
The left hand side contains the same type of expression I used to end the last post:
(A sub 1 AND A sub 2) OR (B sub 1 AND B sub 2 AND (C sub 1 AND C sub 2)) OR (D sub 1 AND D sub 2)
Populating the Form
Our web app is written in CodeIgniter/PHP. For data going into the view, we need three objects:
- An array of JSON objects containing the data to populate the rules
- A JSON object representing the criteria options
- A JSON object representing the action options.
Here’s a simple example. Suppose that this rules engine were being used to govern the routing of system alerts. Suppose that I wanted to receive an SMS any time there was an alert on Server X. Here’s how the rule would look on the UI:
For data going into the view, we’d have those three objects. The first would be the rule itself:
[ { "criteria_group": { "condition": "AND", "rules": [ { "id": "Server", "field": "Server", "type": "string", "input": "text", "operator": "equal", "value": "X" } ] }, "action_group": { "condition": "ACTIONS", "rules": [ { "id": "Alert Destination Type", "field": "Alert Destination Type", "type": "string", "input": "select", "operator": "equal", "value": "sms" }, { "id": "Alert Destination", "field": "Alert Destination", "type": "string", "input": "select", "operator": "equal", "value": "Jonas Schreiber" } ] } } ]
The second object would represent the criteria filters:
[ { "id": "Alert Type", "type": "string", "input": "text", "operators": [ "matches", "not_matches" ] }, { "id": "Alert Message", "type": "string", "input": "text", "operators": [ "matches", "not_matches" ] }, { "id": "Server", "type": "string", "input": "select", "operators": [ "equal", "not_equal" ], "values": { "3": "W", "7": "X", "23": "Y", "27": "Z", } }, { "id": "Account ID", "type": "string", "input": "text", "operators": [ "equal" ] }, { "id": "Count", "type": "integer", "input": "text", "operators": [ "greater_or_equal", "less_or_equal", "equal" ] } ]
The third object is the actions filters:
[ { "id": "Alert Destination Type", "field": "Alert Destination Type", "description": "Send Alert Via This Mode", "type": "string", "input": "select", "values": { "email": "email", "sms": "sms" }, "operators": [ "equal" ] }, { "id": "Alert Destination", "field": "Alert Destination", "description": "Send Alert To", "type": "string", "input": "select", "values": { "Abigail Abernathy": "Abigail Abernathy", "Ben Brigham": "Ben Brigham", "Jonas Schreiber": "Jonas Schreiber", }, "operators": [ "equal" ] } ]
Persistence
Getting at this data is fairly easy using CodeIgniter/PHP, as our data model was very logically laid out. We just make use of tailored queries with joins and a lot of json_encode.
For data going back to the controller, we won’t need the filters, just the rules as they exist on the page.
This is, again, that JSON:
[ { "criteria_group": { "condition": "AND", "rules": [ { "id": "Server", "field": "Server", "type": "string", "input": "text", "operator": "equal", "value": "X" } ] }, "action_group": { "condition": "ACTIONS", "rules": [ { "id": "Alert Destination Type", "field": "Alert Destination Type", "type": "string", "input": "select", "operator": "equal", "value": "SMS" }, { "id": "Alert Destination", "field": "Alert Destination", "type": "string", "input": "select", "operator": "equal", "value": "Jonas Schreiber" } ] } } ]
When you have multiple nesting levels you need to ensure the adjacency list model is correctly maintained. This was a frustrating problem, but we’ve accomplished this with PHP’s RecursiveIteratorIterator. For more about that, and our eventual solution, see this thread.
Leave a Reply