Using Lava Entity Commands to Keep up on your Members' Health

  • By Michael Garrison 2 Years Ago

Before Rock, our church's solution for keeping track of who needed to be visited due to an illness, disablement or being hospitalized was ... a white board. Yep, with thin black tape marking off a grid for the different bits of information needed to keep people up-to-date on their current status.

As you can imagine, after a number of years of this system, it was rather unused (I think one or two names were pretty much on there permanently) and certainly ignored by most of staff. Hospitalization notices were handled over e-mail or in person, pastors were in danger of calling someone for a cheery reminder about the upcoming softball season without being aware that the member had just broken their hip (since information about their status never made it into the old ChMS), and chaos generally reigned.

OK, I might be making the truth a bit more dramatic than it actually was, but the whiteboard was ... not ideal. We needed a modern version- something that was available while off-campus, ensured that notes got posted to members' profiles in Rock, and which was in front of people day after day.

Rock was that solution, as you may have guessed: we developed a workflow that met our needs perfectly and it's been very well-received. But as you may know if you've ever tried to make a report of workflows, prior to v6 it was very difficult to get at the workflow's attribute values (which is where all of the juicy information is stored). Fortunately, the new version of Lava is here, and it makes the "dashboard" side of the equation very straightforward.

Note:
This article has been updated (December 2017) to eliminate some 'legacy' Lava syntax I originally published it with. But the screenshots may not match the Lava I provide since they haven't been updated. When in doubt, copy the text from this article rather than re-typing from the images.

Overview of the Workflow

The workflow is the heart of this process. And although you may be reading this to generally learn more about Lava v2 or workflow reporting, I've run into several churches now that can really benefit from this specific workflow, so I'm going to go through it. It's long, so I'm going to give an overview first

StatusBoardOverview.jpg

This workflow has 5 activities:

1. Collect the initial information
2. Persist the workflow and post the initial notes
3. Wait around for more information
4. Log their release when it's time, and close the workflow
5. Note a contact event on-demand

In general, 1-2 run once at the beginning of the workflow, the workflow lives in 3 almost all of the time, and 4 closes the workflow. Activity 5 is a special case- a bit of sleight-of-hand that we're going to pull, combined with some Rock built-in brilliance: it allows us to log a subset of information without requiring all of the information from activity 3. It's run on-demand and ends itself without (apparently) involving or disturbing activity 3.

Let's go over HOW we wanted the board to work- this will all make more sense once you understand that.

When someone is hospitalized, or injured, or sick, or becomes homebound, we need to add them to the board so staff can get updates and see their status. So we launch the workflow by prompting for, at minimum, the person who needs visiting. Optionally, they can provide anything else that's known: contact information (many hospital rooms have direct numbers), location & room number, date admitted, the ailment, and a note to keep staff updated. The date, reason and location (if provided) then get entered onto the person's profile as a special type of note that's not generally available- something to distinguish it. That way the information is available even after the workflow is closed, as part of their record. The "short note" field doesn't get put on their profile because that's intended for things like visiting hours and days- nothing that needs to be known once they're released.

StatusBoard-A1EndUser.PNG

Once that's entered, we wanted the workflow to wait around for updates- as you can imagine any of the above information typically changes several times throughout a hospital stay, so that needs to be able to entered. We don't need to track the old information- only the CURRENT information matters to the workflow, since it's how a pastor will arrange to visit the person.

StatusBoard-A3EndUser.PNG

These updates are not copied to a profile note, since they largely don't matter in the long run- only for the short term while the workflow is active. So the workflow continues to be the only place that information lives.

Hopefully you're with me so far- obviously the orange button will store the information, run a few actions and then re-open the same activity, where the green button will finish activity 3 and start activity 4, where their release date and notes are recorded and copied to their profile, before the workflow finishes.

StatusBoard-A4EndUser.PNG

Here's the trick though- we wanted to also be able to see the last time someone visited them so if the time gets too long, someone will see it and schedule another visit. Also usually after a visit there will be a status update that should get copied to their profile. So we need another way to sneak into the workflow and update a "Date Last Visited" attribute that's not shown in the above form. That's where Activity 5 comes in. It's a really quick form for the end user, originally only prompting for an optional note on their current condition. But our church also wanted to be able to quantify "number of hours spent by our staff visiting people this year", so that is also requested now in this form. You can leave that off if you don't share the desire.

StatusBoard-A5EndUser.PNG

And that's it.

Preliminary work

Before we jump into the workflow, there are two custom items that will be helpful to have already created. One is a new button style, and the other is a new note type.

HTML Button styles are brilliantly defined/customizable using Defined Types. So head over to Admin Tools -> General Settings -> Defined Types and scroll down to Global/Button HTML. Click on that and then add a new style called "Hidden". Here's what I used for each of the other three fields:

Description:

An invisible button, used in Workflows where you want to be able to use the ?command=[button] URL parameter without actually exposing the button to end users otherwise.

Button HTML:

<a class="btn hidden" onclick="{{ ButtonClick }}" href="{{ ButtonLink }}" data-loading-text="">
    {{ ButtonText }}
</a>

Button Email HTML:

<a style="display: none;" href="{{ ButtonLink }}">{{ ButtonText }}</a>

There, now you've got a "hidden" type button. I know this seems rather useless (who needs a button you can't see or press?), but you'll see how we use it below

For the other item, creating a new note type, I recommend you follow the tutorial in the Rock Admin Hero Guide - use whatever color you want, and assign it a distinctive icon like (fa fa-bed). The note type should not be user selectable.

Creating the workflow step-by-step

Set up the workflow

Create a new Workflow Type (Admin Tools -> General Settings -> Workflow Configuration, then choose or create a category and click (+) Workflow Type). Give it a name, description and icon as you desire. I changed the work term to "note" because a list of "5 notes" made more sense to me than "5 works". You could also use "person" or "patient" if you feel that's more appropriate. The main thing is to make sure that Automatically Persisted is turned OFF, so that you don't get lots of blank rows showing up in your report as people mistakenly launch a workflow and then back out.

You need workflow attributes for everything you want to track as well. It doesn't matter what you set to "Show in grid": we're going to create a custom report anyway. You can assume that anything not noted below can be left at the defaults when you're defining the workflow attributes.


Attribute Description Key Field Type Required Default
Person The person who has been hospitalized Person Person
Location Which facility the person is at Location Text
Room number The room number they're staying in, for visiting and calling purposes Room Text
Phone number The phone number they can be reached at, which is quite likely to be different from their regular contact information Phone Text
Reason The reason for their hospitalization. This will be entered as a note as follows: "This person was admitted for [...]" Reason Text
Date Admitted The date they were admitted to the facility, which is possibly different from the date we found out. Dateadmitted Date
Brief note A brief note that will be visible on the board- visiting hours, times to avoid, etc. Note Text
Date Released The date they were released to go home DateReleased Date
Release note Any notes about their release that staff should know: these will be entered as a note on their profile. Releasenote Memo
Last contact The last date that contact was made (visit or phone call) Lastcontact Date Time
Visit time The total number of minutes that people have spent visiting this person Visittime Integer 0

Here's a screenshot of how the Workflow details and attributes sections look on my system:

StatusBoard-WFSetup.png

Activity 1

I called the first activity "Collect Information". It could also be "Open Workflow" or whatever makes the most sense to you. This activity needs to be both "Active" and also "Activated with Workflow".

There's only a single action in this activity: a User Entry Form. This action needs to mark the "Activity Completed on Success". I do not have this send an e-mail, but that would be an option if you wanted someone to be notified whenever someone was entered (in this case you'd need another action before the form assigning them to this activity).

The following Form Fields should be set to Visible and Editable, only "Person" should also be marked as "Required": Person, Location, Room number, Phone number, Reason, Date Admitted and Brief Note.

You only need a single "Submit" button at the bottom, this should activate activity 2 (called "Log their stay" in this example). There's no need for anything like a cancel button- the workflow isn't automatically persisted, so the new workflows only sticks around if they actually click "Submit".

StatusBoard-A1Setup.PNG

Activity 2

I recommend a name like "Log their stay" for the second activity, it's also a pretty easy one. This will just persist the workflow and create a note on the person's profile before activating activity #3

Your first Action should be a "Workflow Persist" action- it does not need to persist immediately.

The second action is a "Person Note Add" action, using the "Person" attribute. Select the note type you want to use (see the Rock Admin Hero Guide for a walkthrough of creating new note types). I used the caption of "Hospitalized" because generally it doesn't matter or need to be shown WHO it is that's reporting that they're in the hospital - but you want it otherwise, you can also leave this blank to have the reporter's name show up as the caption. (note you can always check the workflow log to see who did open the workflow). The text of the note should be as follows:

{% assign DateAdmitted = Workflow | Attribute:'Dateadmitted' %}
{% assign Location = Workflow | Attribute:'Location' %}
{% assign Reason = Workflow | Attribute:'Reason' %}
Admitted{% if DateAdmitted <> '' %} on {{ DateAdmitted }}{% endif %}{% if Location <> '' %} to {{ Location }}{% endif %}{% if Reason <> '' %} for {{ Reason }}{% endif %}

This checks for the existence of each of the three attributes to be included in the note and rewords the note nicely if any of them don't exist.

Leave the author blank and make sure that the note is not set up to be an alert: you don't want a broken leg to always be the first note on their profile years down the road!

Finally for the third action, we just need to use "Activate Activity" and point it to the third activity, here called "Collect updated information". This action should mark the activity completed on success.

StatusBoard-A2Setup.PNG

Activity 3

Now we're into the meat of the workflow. Fortunately, this is a very simple 2-action activity.

Start by creating a "Workflow Set Name" action to rename the workflow so it's identifiable and logical (it also combines two attributes into a nice single display). The Text Value to use is {{ Workflow | Attribute:'Person' }} - {{ Workflow | Attribute:'Reason' }} The first time this activity is activated (moments after the reporter hits "Submit" in activity 1), it will simply rename the workflow based on the information they provided. But it will also run each time someone updates the workflow, so if the "Reason" changes (they went from surgery to PT, for instance), the workflow name that we'll use in our custom report will stay current.

Action 2 will be another "User Entry Form" action. This is set up exactly the same as the form in Activity 1, except that "Person" should be neither editable nor required: only Visible should be selected on this row this time.

This time, there are going to be 3 buttons at the bottom however

Command Label Button Type Activate Activity Response Text
Update Primary Collect updated information (Activity 3) Your information has been submitted
Person is released Success Note release (Activity 4)
contact Hidden Contact made (Activity 5)

I've numbered the activities to activate, in case you named them differently than I did- it won't actually have that in the label

Now you may have noticed that the third button is how we enter the "contact made" activity, but we're using the "Hidden" button type that we created at the beginning of this article, so that button doesn't appear in the screenshots I've posted.

So if the button is invisible, how ARE we going to get into that 5th activity? That's where the brilliance of Rock comes in; whenever the third activity is active (which is almost always), you can append ?command=[button] to the URL (replacing [button] with the name of a button) to tell the workflow to proceed as if that button were pushed. Even if the button is hidden. So we'll provide a link with that string already appended and the users will never even know that they just completed activity 3 and jumped to activity 5. It's super slick. More on that when we build the report/status board itself.

StatusBoard-A3Setup.PNG

Activity 4

This activity serves to note that the person should no longer be listed on the status board by noting the release and closing the workflow - it gets activated when someone pushes the "Person is released" button in Activity 3.

The first action is another User Entry Form. It displays "Person" (again without allowing editing) as well as "Date Released" and "Release Note" (both of which should be editable and required). It needs only the default "Submit" button

The second action should be set to mark "Activity is Completed on Success", and should be the "Person Note Add" action. Once again, use the "Person" attribute and your custom note type. I use the caption of "Released" with no author (since, again, it doesn't really matter or need to be seen WHO is marking them as released). It should also not be an alert type note. The text should be something like Released on {{ Workflow | Attribute:'DateReleased' }}: {{ Workflow | Attribute:'Releasenote' }}

StatusBoard-A4Setup.PNG

Activity 5

This activity is by far the most complex. Remember it's designed to do two things: take any updates that should be posted from the conversation, and store the amount of time they visited with the patient.

In order to do this though, we need a few more Activity Attributes (unlike Workflow attributes, these get reset every time the activity is started and are only available within this activity...meaning we don't have to worry about this activity being run multiple times).

Attribute Description Field Type Required Default value
ContactNote A note indicating how you contacted them and how they're doing - this will be posted to their profile as a note for others to see. Memo
Command Selected Stores which button was pressed in the "Just made contact" form Text
Visitor Stores who filled out the "Just made contact" form Person
Visit length (minutes) * Used to track time spent on visitations by all staff together Integer 10

* I gave this attribute a Key of Newtime to keep it straight in my mind between the total time and the newly-added time. Change the key if you wish, but you'll have to update the lava to match in Action 6 below.

So we'll start with the first action being one final User Entry Form. This form will show and allow editing of only "Contact Note" and "Visit Length (minutes)". But since our "main loop" (Activity 3) is presently not active, we need to allow a way to get back to that main activity, if they don't want to submit this update. So provide two buttons at the bottom: the default "Submit" button and also a "Cancel" button (of type Secondary). The Cancel button should activate the "Collect Updated Information" activity (the third activity), while the submit button should just give a response text.

It's very important that you set "Command Selected Attribute" to your "Command Selected" activity attribute- if they click cancel the third activity will indeed be activated but the current activity will ALSO continue running - we need to stop that.

For that reason, our second action will be of type "Activity Complete" - but then click the filter icon and tell it to only run if the "Command Selected" attribute is "Equal To" the text value of "cancel". That way IF they clicked cancel, this action will run and stop the rest of the activity from running. If not, activity 5 will run through to the end.

So by the time they get to the third action, we know that they have indicated they met with the person - now we'll take note of everything and update attributes as required.

We want to know who it was who made contact, so the third action will get that using the "Attribute Set to Current Person" to set the "Visitor" person attribute.

Now we have what we need to store the profile note. Action 4 is of type "Person Note Add". Set it to the "Person" attribute, set your custom note type, with the author set to the "Visitor" attribute. (Unlike the opening and closing notes, we've found it IS useful to be able to see who made contact and is reporting on their current condition on the profile note). As always, it should not be an alert note. The Caption should be {{ Activity | Attribute:'Visitor' }} | Hospitalization contact made and the Text should be {{ Activity | Attribute:'ContactNote' }}

In Action 5 we can finally update the workflow attribute which indicates the last time contact was made with the individual, so use the "Attribute Set Value" action to set the "Last Contact" attribute to the current date using {{ 'Now' | Date:'yyyy-MM-dd HH:mm:ss' }}

Finally, we'll use Action 6 to track the time they spent visiting the person. Once again, use the "Attribute Set Value" action to set the "Visit time" attribute using {% assign vt = Workflow | Attribute:'Visittime' %}{% assign nt = Activity | Attribute:'Newtime' %}{{ vt | Plus:nt }}

OK, now we've done everything that needs doing when someone makes contact- we just need to return the workflow to the main loop so that the overarching information can be updated. Action 7 should therefore mark the activity completed on success, and be of type "Activate Activity": activate the "Collect updated information" activity (activity 3).

StatusBoard-A5Setup.PNG

That's the end of the workflow- save it and test it if you'd like to- before we create our custom report, you can use the workflow list to see where your tests are at in the workflow and see what the variables hold. While you're on the page where you can edit or list the workflow, look at the URL and take note of the WorkflowTypeId. The rest of this article uses the number 27, so replace that wherever it occurs with your own WorkflowTypeId.

Creating your dashboard/report block

Here's where we jump into Lava v2, which is part of Rock v6 (actually 1.6 if you're paying close attention). Our church had been using some hardcoded SQL in a Dynamic Data block to report on people currently "active" in this status board, querying for the attribute values by AttributeId. It's not pretty for a post like this. Fortunately, the new version of Lava brings the strength of Entity commands, of which Workflows are one. You're going to love this.

So if you're running Rock McKinley 6 or higher, go to the page you wish to have your status board on and create an HTML block. Edit the settings to allow Entity commands, set the cache time to 0, and let's step through what we're going to do:

Start by creating a panel block div; this will help our report visually match the rest of the currently active Rock theme:

<div class="panel panel-block">
	<div class="panel-heading"><h1 class="panel-title"><i class="fa fa-bed"></i> Health status board</h1></div>
	<div class="panel-body" style="overflow-x:scroll;">

Now let's create the table with headers for the fields we want to display:

		<table class="grid-table table table-bordered table-striped table-hover">
			<thead>
				<tr><th>Name:</th><th>Location:</th><th>Room Number:</th><th>Phone number:</th><th>Date admitted:</th><th>Last contact:</th><th>Note:</th><th>Actions:</th></tr>
			</thead>
			<tbody>

Here is where it gets fun, let's set up our Workflow command and loop through workflows of the "Health Status Board" type (replace WorkflowTypeId == 27 with your own as noted above) where the status is "Active" (i.e. not closed):

				{% workflow where:'Status == "Active && WorkflowTypeId == "27"' sort:'Id desc' %}

Now we'll loop through all of the workflows found and create a table row for each workflows, and a cell to display the information we want to show. Note that we're going to make sure the title of the workflow is a link to update the information by linking it to the default /WorkflowEntry/27/ route (you will need to edit the 27 to reflect the actual workflowtypeid for your workflow). We also need a way for people to indicate they have made contact and jump to Activity 5 - remember we created that hidden button whose value we can include in the URL to jump from Activity 3 to Activity 5 - we'll include that in the "Contact Made" button link at the end of the rows.

					{% for workflow in workflowItems %}
						<tr>
							<td><a href="/WorkflowEntry/27/{{ workflow.Id }}">{{ workflow.Name }}</a></td>
							<td>{{ workflow | Attribute:'Location' }}</td>
							<td>{{ workflow | Attribute:'Room' }}</td>
							<td>{{ workflow | Attribute:'Phone' }}</td>
							<td>{{ workflow | Attribute:'Dateadmitted' | HumanizeDateTime }}</td>
							<td>{{ workflow | Attribute:'Lastcontact' | HumanizeDateTime }}</td>
							<td>{{ workflow | Attribute:'Note' }}</td>
							<td><a class="btn btn-default btn-sm" href="/WorkflowEntry/27/{{ workflow.Id }}?command=contact">Contact<br>made</a></td>
						</tr>
					{% endfor %}

Now close the loop and table out

    			    {% endworkflow %}
    			</tbody>
		</table>

Before we close out the divs, we need one more thing: a button to add a person (workflow) to the list. Once more, be sure to replace 27 with your own WorkflowTypeId:

		<a class="btn-add btn btn-default btn-sm pull-right" href="/WorkflowEntry/27"><i class="fa fa-plus-circle"></i></a>
	</div>
</div>

All together now:

<div class="panel panel-block">
	<div class="panel-heading"><h1 class="panel-title"><i class="fa fa-bed"></i> Health status board</h1></div>
	<div class="panel-body" style="overflow-x:scroll;">
		<table class="grid-table table table-bordered table-striped table-hover">
			<thead>
				<tr><th>Name:</th><th>Location:</th><th>Room Number:</th><th>Phone number:</th><th>Date admitted:</th><th>Last contact:</th><th>Note:</th><th>Actions:</th></tr>
			</thead>
			<tbody>
				{% workflow where:'Status == "Active && WorkflowTypeId == "27"' sort:'Id desc' %}
					{% for workflow in workflowItems %}
						<tr>
							<td><a href="/WorkflowEntry/27/{{ workflow.Id }}">{{ workflow.Name }}</a></td>
							<td>{{ workflow | Attribute:'Location' }}</td>
							<td>{{ workflow | Attribute:'Room' }}</td>
							<td>{{ workflow | Attribute:'Phone' }}</td>
							<td>{{ workflow | Attribute:'Dateadmitted' | HumanizeDateTime }}</td>
							<td>{{ workflow | Attribute:'Lastcontact' | HumanizeDateTime }}</td>
							<td>{{ workflow | Attribute:'Note' }}</td>
							<td><a class="btn btn-default btn-sm" href="/WorkflowEntry/27/{{ workflow.Id }}?command=contact">Contact<br>made</a></td>
						</tr>
					{% endfor %}
				{% endworkflow %}
			</tbody>
		</table>
		<a class="btn-add btn btn-default btn-sm pull-right" href="/WorkflowEntry/27"><i class="fa fa-plus-circle"></i></a>
	</div>
</div>

Congratulations- you should now have a fully-functional health status board, and have learned a bit of Lava V2 in the process!

HealthStatusBoardReport.jpg

Appendix:

I mentioned the possibility of totaling the "visit times" across all workflows earlier. If you want to do this, you can use the following Lava:

{% assign timetotal = 0 %}
{% workflow where:'WorkflowTypeId == "27"' %}
    {% for workflow in workflowItems %}
        {% assign thistime = workflow | Attribute:'Visittime' %}
        {% assign timetotal = timetotal | Plus:thistime %}
    {% endfor %}
{% endworkflow %}
{{ timetotal }}

You can even add && ActivatedDateTime > "{{ 'Now' | Date:'yyyy' }}-01-01" inside the where clause of the workflow loop, to get only visit time on people who were admitted this year.

If you wanted to get really specific on tracking when visit times occurred, you could set up a new "Manual" type cumulative metric and add a Run SQL action in activity 5 that would run INSERT INTO [MetricValue] to add the metric length. If you're interested in this, jump into Slack and ask in the SQL channel or message me directly. It's a little more involved than I want to cover in a general blog post: SQL commands are not to be taken lightly. =)


@mikejed
Spark Development Network
Flagstaff, AZ

Michael Garrison recently left his job in Architecture to become one of the "new guys" at Spark (the "Core Team"), but he's still helping out Christ's Church of Flagstaff and other non-profits with tech needs in his off-hours, trying to make computers do what computers do best, so that people are freed to do what we do best: relate with people!