11 Finding and Fixing Legacy Lava Shared by Brent Pirolli, CedarCreek Church 5 years ago 7.0 General, Web Intermediate What is Legacy Lava: "Legacy Lava" is when you use attributes as if they were a property. *record skip* "Whaaaat? What in the world does that mean?" you may ask. I'm glad you asked. It is acceptable if you have lava that looks like this: {{ Person.PropertyName }} It is no longer acceptable if you have lava like this: {{ Person.AttributeName }} While properties can still be called as in the first line, attributes must now be called like this: {{ Person | Attribute:'AttributeName' }} This new method uses an attribute filter instead. "Properties... Attributes... Wait... What's the difference?" A Quick Lingo Lesson Let's learn some basic lingo to get started. Entity: The word "entity" is used to describe the classification unit of different types of data in Rock. For instance, People, Groups, Financial Transactions, Locations and Pages are all entities in Rock. If you’re familiar with databases, entities are very similar to tables. In fact, most entities in Rock have an associated table in the database. Property: Entities have Properties. If an Entity is a database table, the properties are the columns in that table that are storing data. Look at the "Admin Tools - Power Tools - Model Map" and you can find a complete list of every property an entity has. For example in the CRM section of the Model Map we see the Person Entity. If you click on Person on the left, you see FirstName, LastName, Email, PhotoId... and many others, which are properties of the Person Entity. This means they can be called by using lava such as {{ Person.FirstName }}. Attribute: Everything stored in Rock can have attributes added to it. For example, we can add numerous attributes to a person according to what's important to your organization. If you wanted to track if a person had a pet zebra, you could add a "HasZebra" Person Attribute under the "Admin Tools - General Settings - Person Attributes" section. But since this is an attribute, you have to call it as {{ Person | Attribute:'HasZebra' }} now. Why does having legacy lava matter? Rock was changed to call attributes differently because it wasn't sustainable for the growth of a Rock install. As attributes are added for various reasons, over time, the code that loops through checking entity properties has to check through all the attributes each time as well. This can mean drastically slower performance. But if we can tell Rock to only loop through a smaller "dictionary" of code and we can call it to look for specific attributes as needed, it speeds things up considerably. So while this old method was supported in v5, it was changed in v6, and will be fully obsolete and ignored by v16. If you check your "Admin Tools - General Settings - Global Attributes" you will find an entry called "Lava Support Level" which has one of 3 values (depending on your version): Legacy, LegacyWithWarning, or NoLegacy. Legacy: Operate like early versions of Rock. This is deprecated and removed in v10. LegacyWithWarning: Operate like early versions of Rock, but put a warning in the "Admin Tools - System Settings - Exception List" alerting you to the existence of legacy lava that needs updated. This will be deprecated and removed in v16. NoLegacy: Any legacy lava in the code will be ignored and not function properly and there will be no exceptions logged. Having this setting will result in faster site performance than the other two options. How do I clean up legacy lava? 1) If you aren't yet on "LegacyWithWarning" you should change to that now to be alerted of where it is being encountered on your site. Go to "Admin Tools - General Settings - Global Attributes" and set the "Lava Support Level" to "LegacyWithWarning". (I believe you may need to restart rock for this to take effect, or wait until the next day after the AppPool recycles at night.) 2) Check your exceptions in the "Admin Tools - System Settings - Exception List" for errors. They will be logged starting with "Warning: Legacy Lava Syntax Detected:" and then tell you what was logged. Most of them are probably in old (even core) workflows like the contact form. Here is an example of one on our site: 3) If you click the Exception in the Exception List, you will see a list of all occurrences of legacy lava being found on the site. This may not be everywhere it exists, but it is everywhere it has been loaded since logging was enabled. We're going to focus on this example still: 4) If you click on the Exception occurrence in the step above, you will see specifics about that event, including the page it was detected on. This is where we must go to fix this particular issue as it is HTML in a block causing our problem. If it were in a workflow or other area, you may need to visit that workflow setup, and not the page it was detected on, to fix the issue. 5) Fix the code. If your exception shows you it was on a certain page or in a certain workflow, find that code and edit it to change the legacy attribute reference into the correct format. Our example above used a Person attribute of "Student20182019Status" but to keep it simple, let's look at this other example of legacy lava where a user has made "HasZebra" as an attribute on a person record: Does this person have a zebra? {{ Person.HasZebra }} Here we expect to see the output of either "Yes" or "No" for the lava of {{ Person.HasZebra }}, but this will throw a legacy lava error as "Person.HasZebra" is trying to call an attribute written as if it were a property. It needs to be written as {{ Person | Attribute:'HasZebra' }} to be valid going forward. So we change it to: Does this person have a zebra? {{ Person | Attribute:'HasZebra' }} The output is the same, but now our code is correct and no longer "legacy lava" based. Let's look at another example where we have an IF statement involved. The following code will see if the attribute has a value (is not empty... so in our case it is a Yes or a No), and then it displays that Yes or No. If the attribute is empty, nothing is displayed. {% if Person.HasZebra != empty %} {{ Person.HasZebra }} {% endif %} In this case, there is a catch. You can never use filters inside of an IF statement and {{ Person | Attribute:'HasZebra' }} is filtering by the attribute of HasZebra. So {% if {{ Person | Attribute:'HasZebra' }} != empty %} won't work. Instead, you would re-write it as such, assigning a variable named whatever you'd like to that attribute filter (I'd encourage a more descriptive name, but for our example let's use "butterbeer" for funsies), and then running your IF against that variable name: {% assign butterbeer = Person | Attribute:'HasZebra' %} {% if butterbeer != empty %} {{ butterbeer }} {% endif %} What if your legacy lava has a filter on it already? Glad you asked! In the case of {{ Person.HasZebra | Upcase }} we could change it to {{ Person | Attribute:'HasZebra' | Upcase }} to get rid of the legacy lava. What if your legacy lava is in SQL Queries? For the most part, the fixes are identical to what we've covered above. There are some things that may come up like when you tell SQL how to return the value such as in this snippet AND A.[Guid] = '{{ Workflow.DateAttribute_unformatted }}' where we would replace the code with AND A.[Guid] = '{{ Workflow | Attribute:'DateAttribute','RawValue' }}' instead. So you can see the similar approach to replacing the lava code where Workflow.DateAttribute became Workflow | Attribute:'DateAttribute' instead, but the _unformatted didn't directly translate and we had to add that as an additional element by adding ,'RawValue'. Hopefully this is enough to get you there. If you get stuck on something, always feel free to ask in the Lava channel of the chat. 6) Test your new lava code to ensure it all still works as desired! If so, congratulations on fixing your legacy lava! Now monitor your Exception Log regularly to fix other instances that pop up. Once you are no longer seeing legacy lava errors with new timestamps, feel free to flip the "Admin Tools - General Settings - Global Attributes" setting for "Lava Support Level" to "NoLegacy" and enjoy the extra site speed! Before you do this however, you may want to do a little more probing to ensure you don't have any extra use of that same legacy lava that just hasn't been stumbled on yet. Using the tool below, you can take some of the ones you did log, and search for them in other areas of the site. A Handy Weapon Chris Rea has written a handy SQL script that can help you hunt down legacy lava as well instead of just waiting for it to appear on your exception log. If you go to "Admin Tools - Power Tools - SQL Command" you can paste this script in and change the ".HasZebra" text to whatever legacy lava tag you found in your exception log and when you run it, you will be given a report of every page on your site that contains that specific legacy lava. You can then export that result into Excel for a better view of the results. It shows the Location, Entity ID, Location Name, Workflow Type ID, Attribute Name, Found Value, and even the line of code that was found! It's pretty incredible at helping track down where things need updated. To be clear, this SQL doesn't FIX it for you, and no changes are made to your data using it. It just outputs a list of results. -- Production Declare @filterValues varchar(max) = '.HasZebra' --Declare @filterValues varchar(500) = null --'Value A,Value B,Value C,Value D,Value E' Declare @includeWorkflowTypeIds varchar(500) = null --'86,87' Declare @excludeWorkflowtypeIds varchar(500) = null --'55,57' Select * From ( Select 'Workflow' as [Location] , wt.Id as [EntityId] , wt.Name as [LocationName] , wt.Id as [WorkflowTypeId] , a.Name as [AttributeName] , y.FoundValue as [FoundValue] , a.DefaultValue as [Value] From [WorkflowType] wt Join [Attribute] a on a.EntityTypeQualifierValue = wt.Id and a.EntityTypeQualifierColumn = 'WorkflowTypeId' Join [EntityType] et on et.Id = a.EntityTypeId and et.FriendlyName = 'Workflow' Join ( Select '%' + x.value + '%' as [LikeArgument], x.value as [FoundValue] From string_split( @filterValues, ',' ) x ) y on a.DefaultValue like y.LikeArgument Union All Select 'Activity' as [Location] , wact.Id as [EntityId] , wt.Name + ' > ' + wact.Name as [LocationName] , wt.Id as [WorkflowTypeId] , a.Name as [AttributeName] , y.FoundValue as [FoundValue] , a.DefaultValue as [Value] From [WorkflowActivityType] wact Join [WorkflowType] wt on wt.Id = wact.WorkflowTypeId Join [Attribute] a on a.EntityTypeQualifierValue = wact.Id and a.EntityTypeQualifierColumn = 'ActivityTypeId' Join [EntityType] et on et.Id = a.EntityTypeId and et.FriendlyName = 'Workflow Activity' Join ( Select '%' + x.value + '%' as [LikeArgument], x.value as [FoundValue] From string_split( @filterValues, ',' ) x ) y on a.DefaultValue like y.LikeArgument Union All Select 'Action' as [Location] , wactiont.Id as [EntityId] , wt.Name + ' > ' + wact.Name + ' > ' + wactiont.Name as [LocationName] , wt.Id as [WorkflowTypeId] , a.Name as [AttributeName] , y.FoundValue as [FoundValue] , av.value as [Value] From [WorkflowActionType] wactiont Join [WorkflowActivityType] wact on wact.Id = wactiont.ActivityTypeId Join [WorkflowType] wt on wt.Id = wact.WorkflowTypeId Join [AttributeValue] av on av.EntityId = wactiont.Id Join [Attribute] a on a.Id = av.AttributeId and a.EntityTypeQualifierColumn = 'EntityTypeId' Join [EntityType] et on et.Id = a.EntityTypeId and et.FriendlyName = 'Workflow Action Type' Join ( Select '%' + x.value + '%' as [LikeArgument], x.value as [FoundValue] From string_split( @filterValues, ',' ) x ) y on av.Value like y.LikeArgument ) x Where ( 1 = Case When x.WorkflowTypeId in ( Select cast( x.Value as int ) From string_split( @includeWorkflowTypeIds, ',' ) x ) Then 1 When @excludeWorkflowtypeIds is not null and x.WorkflowTypeId not in ( Select cast( x.Value as int ) From string_split( @excludeWorkflowtypeIds, ',' ) x ) Then 1 When @includeWorkflowTypeIds is null and @excludeWorkflowtypeIds is null Then 1 Else 0 End ) Order by 3 asc