/ Development  

Time to review advanced expressions during entity modeling

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

For this week we dive into interesting expressions from the entity modeling perspective of the platform. During all my posts and knowledge-share articles, I mentioned you all about the expression chapter of the low-code guide of the platform “Chapter 12 - Using expressions”. I did a search within my own articles and found two related posts about these types of expressions (a new “Dynamic list filtering” feature, and the field validation). For this post we just continue the grind where we try other advanced expressions.


Let get right into it…

Time for a quick recap and figure out where we can even use an entity modeling expression and how we can quickly put them to the test to evaluate its value. For this I see these entrances:

  1. Rule BB set property field
  2. Rule BB condition (advanced editor)
  3. Security condition (behind the filter icon)
  4. List BB (as a filter option)
  5. Deadline (start/end policy)
  6. Activity flow (start condition)
  7. Lifecycle (transaction with conditional event)
  8. Email template!

Comment me on other entrances if I missed one? I know there is also a filter (with parameter option) on the ‘Web service’ BB when you add a new ‘Find’ operation, but I leave it out of scope for this post. It doesn’t seem to support these type of expressions like described below!

What I also leave out of scope is the expression editor we can find in the message map of a BPM!

Grab yourself a copy of the expression system.organization which retrieves the current organization name as a value. In my case appworks_tips. Have yourself an entity project available (incl. property prj_name) for the first entrance on Rule BB level (an event type of rule with name e_on_init_set_name):

xpr_001

After a save, publication, and first test in runtime, we have our first result:

xpr_002

Now we’ll add a condition (our second entrance) like this:

xpr_003

Single quotes does the same trick: 'appworks_tips'==system.organization. I know it’s always TRUE in my case, but it’s just to demonstrate the possibility. Try != or <> 🤔

Next is the ‘Security’ BB entrance on the project entity. Add this BB, add the ‘Identity User’ as role, and mark all the options. Finally, you can add a filter condition to for example the ‘Create’ action. Like this:

xpr_004

The condition behind this looks like this (just like we saw before): "appworks_tips"<>system.organization. In this case we just made sure our account isn’t able to create new instances of the entity. Try it out yourself and make your own conclusions.

The next one is a filter on a ‘List’ BB. I use the name lst_filtered_projects, add the prj_name to the list and jump to the ‘Filter’ tab to do something like this:

xpr_005

For you to copy: $(system.organization)

Keep in mind the previous ‘Rule’ BB and ‘Security’ BB as they might interfere if not updated correctly. I removed both of them to have a try in runtime where it works as expected when I create a project entity instance with name appworks_tips!

Time for the ‘Deadline’ BB with name dl_generic:

xpr_006

Trust me…This works exactly the same as the condition for the ‘Rule’ BB!

The ‘Activity flow’ BB with name flw_generic?

xpr_007

That’s all the same! Even for the ‘Lifecycle’ BB it looks the same:

xpr_008

Our final entrance is the ‘Email template’. For this one we require to add the ‘Email’ BB with just a default configuration applied and without saving anything. We don’t even require a mail server as we’re not sending out any mail. When we apply the ‘Email’ BB, you can add a new ‘Email template’ BB with sample name emt_expressions. In the body we add an expression like this:

xpr_009

For you to copy: {system.organization}

To try this one out in runtime, we need to update the default view layout lyt_default of the entity where we add the ‘Emails’ panel on the far-right side. This is the view in runtime:

xpr_010

Create a new mail, use the created template, and check the result:

xpr_011

Nice…Now what?


Other expressions

In the above examples we saw three ways of calling a variable:

  1. Directly in plain text: system.organization
  2. Surrounded by curly brackets: {system.organization}
  3. Prefixed with a dollar-sign and ‘normal’ brackets: $(system.organization)

When to use which one, depends on the context you’re in at that moment. Just try it out!

Time to have a look at other expressions…For this exploration we’ll misuse the final created mail template. I see this is the most interesting way to build a nice list to check the results for a full-blown set of expressions!

This is a compact list of updates I did during my own expression craftsmanship:

  • The project entity updated with extra properties like prj_start_date and prj_duration. You see it passing by in the expressions below.
  • Creating a ‘toChild’ related issue entity with an Integer type of property isu_points and a Boolean type of property isu_is_solved; just to play around with Integer / Boolean expressions.
  • Adding my awdev developer account to a functional role called fun_role1. This role is just a new type of document saved in the ‘roles’ folder of the project. You can use the ‘User Manager’ artifact to apply this role to your account. You’ll find the result for this change in the targetRoles() call below.

Behold this everlasting list of expressions to play around with. They all work (with comments) from my 22.1 AWP environment…Just copy & paste it into your own mail template to validate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#System:
sys.organization: {system.organization}
sys.baseURL: {system.baseURL}
user.name: {user.Properties.Name}
{"\n"}

#Date / Time:
prj.start_date: {item.Properties.prj_start_date}
prj.duration: {item.Properties.prj_duration}
Add duration: {item.Properties.prj_start_date + item.Properties.prj_duration}
Add end of years: {item.Properties.prj_start_date + 1 end of years}
Minus ten days: {item.Properties.prj_start_date - duration("P10D")} //Read more about "P10D" on https://www.w3schools.com/xml/schema_dtypes_date.asp
Add two months, three days: {item.Properties.prj_start_date + duration(2, 3)}
Add end of months, two mondays: {item.Properties.prj_start_date + 1 end of months + 2 mondays}
Date at time: {item.Properties.prj_start_date at time(13, 58)}
Now: {now}
Today: {today}
Never: {never} &#x1F609;
Date: {date(2022, 12, 31)}
Time: {time(13, 58)}
Easter year: {dateEaster(2022)}
Now at time: {now at time(20, 00)}
Add dates from now: {now + 1 end of months + 2 fridays + 1 weeks}
Day of month/week/year: {date(2022, 12, 31).getDayOfMonth() + ' ' + now.getDayOfWeek() + ' ' + now.getDayOfYear()}
Time get hour/minute: {time(13, 58).getHour() + ' ' + now.getMinute()}
{"\n"}

#Simple calc:
{1 + 2} | {1 - 2} | {1 * 2} | {1 / 2} | {3 * (4 / 2)}
{1 > 2} | {1 <= 2} | {1 <> 2} | {1 != 2} | {1 is not 2}
{2 % 4} | {(2 % 4) == 2} | {(2 % 4) is 2}
{1.4 * (1 + 3)}
{2034.0 / 100} | {2034 / 100} | {2034.0 / 100.0} | {2034 / 100.0}
{"\n"}

#String methods:
{"word" == "word"} | {'word' == 'word'}
{substring("hello world!", 0, 8) + "rld"}
{length('test') * 7}
{true && 'true'}| {true and 'true'} | {false || "false"} | {false or "false"}
{"" == blank} | {'' <> blank}
{("hello").toUpperCase()}
{("hello").equalsIgnoreCase("HELLO")}
{("hello").endsWith("o")}
{("hello").indexOf("ll")}
{("hello").length()}
{("hello").matches('^([a-zA-Z]+)$')} //only characters check
{("hello;world").split(';')[1]}
{("hello").startsWith('h')}
{("hello").substring(2, 5)}
{(" hello ").trim()} //is not working as expected?
{"\n"}

#Ternary operator:
{1 == 2 ? 3 + 3 : (1 + 1) % 4} | {1 <> 2 ? 3 + 3 : (1 + 1) % 4}
{"\n"}

#Math:
Average: {average(item.issue[].Properties.isu_points)}
MaxMin: {max(item.issue[].Properties.isu_points)} | {min(item.issue[].Properties.isu_points)}
Median: {median(item.issue[].Properties.isu_points)}
Mode: {mode(item.issue[].Properties.isu_points)}
Optional: {"Hello" + optional(null, " ") + "world"} | {"Hello" + null + "world"}
Random: {random(5, 12)} | {random(min(item.issue[].Properties.isu_points), max(item.issue[].Properties.isu_points))}
Round: {round(4.75)} | {round(item.issue[0].Properties.isu_points)}
Floor: {floor(4.75)} | {floor(item.issue[0].Properties.isu_points)}
Ceil: {ceil(4.75)} | {ceil(item.issue[0].Properties.isu_points)}
{"\n"}

#Functions:
targetUser: {targetUser()}
targetRoles: {targetRoles()[0]} //Apply yourself with a functional role to get a result!
targetAll: {targetAll()[0]}
getCurrentUserLocale: {getCurrentUserLocale()}
typeOfEntity: {typeOf('0800270ee5c1a1eca9a4db2ea3f90471')} //The id can be found in the URL of an entity instance in runtime.
isInRole: {isInRole('appworks_tips prj_aw_tips_gen', 'fun_role1')} //The package name is found in the package properties in the context menu of a project
isInGroup: {isInGroup('all_developers')} //The group name is create and attached to my account from an OTDS partition perspective
getEntityTypeId: {getEntityTypeId('project', 'appworks_tipsprj_aw_tips_gen')} //Use the solution name from `/app/admin`; My example shows null for some strange reason!?
getEntityTypeName: {getEntityTypeName('0800270ee5c1a1eca9a4db2ea3f90471', 'appworks_tipsprj_aw_tips_gen')}
{"\n"}

#Operators:
Logical not: {!true} | {not false}
Bitwise: {-10} | {~10} | {5 & 1} | {5 ^ 1} | {5 | 1} //https://www.programiz.com/java-programming/bitwise-operators
Coerce: {((string) 10) == "10"} | {((integer) "10") == 10} | {"10" == 10} | {((boolean) 'true') == true}
List range: {1 not in item.issue[].Properties.isu_points} | {"hello" contains "q"} | {"hello" does not contain "q"}
Null check: {'' is null}
{"\n"}

#Arrays:
{("hello;in;my;array;world").split(';').size()}
{("hello;in;my;array;world").split(';').indexOf('array')}
{("hello;in;my;array;world").split(';').contains('my')}
{(";").split(';').isEmpty()}
{("hello;in;my;array;world").split(';').get('2')} | {("hello;in;my;array;world").split(';')[2]}
{("hello;in;my;array;world").split(';').substring(0, 2)[3]}
{(item.issue[].Properties.isu_points).size()}
{(item.issue[0].Properties.isu_points) + (item.issue[1].Properties.isu_points)}
{sum(item.issue[].Properties.isu_points)} //this is not working: {sum(('1,2,3').split(','))} because of the String type return!
{sumN(item.issue[].Properties.isu_points)} //null-aware
{countTrue(item.issue[].Properties.isu_is_solved)} //this is also not working: {countTrue(("true;false;true").split(';'))}
{percentTrue(item.issue[].Properties.isu_is_solved)} //0% - 100%

With the result from my test-email:

xpr_012


xpr_013


xpr_014

That’s an interesting list for me (and you) to remember. It will for sure help somewhere in the future! 🔮

Secret “list” expression

In the above example, you saw this expression passing by:
("hello;in;my;array;world").split(';').contains('my')

Works great (also on the other building blocks) but let me tell you a little secret! For conditions in the ‘Rule’ BB and ‘Security’ BB (as well as setting a property value via a ‘Rule’ BB), you can also use that same expression in another format:
'my' in list["hello","in","my","array","world"]

Interesting, as I couldn’t find this list expression in any of the documentation. It doesn’t work in the email test template from above. That’s why I made it a separate section in the post!


Nice, “DONE”, and nailed it! When I review the list again, it provides interesting expressions to play along with in your own solution. I knew expression were possible, but it looks like we’ve exposed expressions beyond the documentation with real-life examples and proven screenshots! Have a momentous week; CU next time…cheers! 🍺

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?