Save/restore component properties to DB

v8.1

Trying to build a couple functions that will allow users to save runtime configurations (e.g. table filtering/columns/sorting/etc) into a DB so they can be sticky between sessions. I have a decent working prototype using copy.deepcopy() in a view's Startup & Shutdown scripts on a single table component.

Now I'd like to move this to a project function so it can be used in many places, something like saveProp(comp,prop) and restoreProp(comp,prop), where the function takes care of building the named query parameters out of the user credentials and supplied properties, comp is an object referencing the component whose properties are being saved/restored, and prop is a string value pointing to the property to save, e.g. props.filters for a table.

I've been using Scripting reference parent property with parameter - Ignition - Inductive Automation Forum for ideas...

I have a few questions/problems:

  1. I'm hoping to keep script execution on the client as much as possible, since our client count may get pretty large and I don't want to burden the gateway. Are project library scripts executed on the client or the gateway?
  2. When my test view calls the project function, I'm getting an error:

AttributeError: 'com.inductiveautomation.perspective.gateway.script' object has no attribute 'getattr'

This is the project function:

def propGet (comp, prop):
	props = comp.getattr(comp, prop)
	print "props: %s" %(props)

This is the calling script, attached to a button on the same view as an alarm status table:

def runAction(self, event):
	comp = self.getSibling("AlarmStatusTable")
	system.perspective.print("comp: %s" %(comp))
	project.functions.propGet(comp, 'props.filters.active.states')

The output console shows this from the calling script:

comp: com.inductiveautomation.perspective.gateway.script.ComponentModelScriptWrapper$SafetyWrapper@153dd4d

Not sure I understand why the component reference includes gateway.script when it's coming from an event script on a button.

Why is getattr() failing?

.

getattr() is a standalone python method. You should be using it as props = getattr(comp, prop) And at that point you don't really need your custom method, unless you want the logging of the value.

This is perspective, so always gateway. Nothing in perspective is executed client side.

1 Like

Well, no scripts or expressions you author. Lots of client side code is running for components, but it's generally speaking not the "source of truth" for component state.

Plan on a beefy gateway, and almost certainly a frontend-backend split, where drivers and tag history are handled on a gateway that doesn't have any Perspective load.

Ok, boneheaded mistake on my part, thanks for pointing out the bad syntax on getattr().

Follow-up question since I'm now running into other issues. I am able to use getattr() and save component properties to the DB. However, when I attempt to restore the properties from the DB to the component, I'm getting errors. I'm using the recommendation from @pturmel in this thread to try both setattr() and setPropertyValue and it looks like both are failing.

project script:

def testwriteprops(comp):
	logger = system.util.getLogger("project.functions.testwriteprops")
	logger.trace("test write comp: %s" %(str(comp)))
	prop = 'props.filters.active'
	value = {u'priorities': {u'high': True, u'critical': True, u'low': True, u'diagnostic': False, u'medium': True}, u'text': u'', u'conditions': {u'provider': u'', u'source': u'', u'displayPath': u''}, u'results': {u'data': [], u'enabled': False}, u'states': {u'activeAcked': True, u'clearAcked': False, u'clearUnacked': True, u'activeUnacked': True}}
	try:
		logger.trace("trying setattr")
		setattr(comp,prop,value)
		logger.trace("used setattr")
	except AttributeError:
		logger.trace("trying setPropertyValue")
		comp.setPropertyValue(prop,value)
		logger.trace("used setPropertyValue")
	logger.trace("filters saved")

calling script on a button:

def runAction(self, event):
	comp = self.getSibling("AlarmStatusTable")
	project.functions.testwriteprops(comp)

Log output:

Error running action 'component.onActionPerformed' on testview@D/root/PasteButton: Traceback (most recent call last): File "function:runAction", line 26, in runAction File "module:project.functions", line 738, in testwriteprops AttributeError: 'com.inductiveautomation.perspective.gateway.script' object has no attribute 'setPropertyValue'

Seems like I'm not doing a proper job of retrieving the component reference, but I don't know why. The logger shows the value of comp as com.inductiveautomation.perspective.gateway.script.ComponentModelScriptWrapper$SafetyWrapper@2f507ffb

The safety wrapper is a Perspective thing. Most of those old techniques are Vision-only.

1 Like

From the SDK docs for ComponentModelScriptWrapper, it notes that the safety wrapper class 'Delegates to the actual component, this prevents the scripting system from being able to call everything on ComponentModel, like startup(), shutdown, etc.'

It looks like there is a PropetyTree class you get from the component that seems to have methods to write to different properties on the component.

What is stopping you from writing these configs to a session custom property or a view custom property on startup? It would be simpler, as you would just dump all the configs into there (separating into additional properties as need) and then just make sure the components that need the configuration are bound to their associated property in that top level configuration.

Thanks for the lead. I've never even seen the SDK before.

The merge method looks promising...will have to experiment. Is there additional documentation on the classes in the SDK (examples, etc.) that would include the PropertyTree class?

If I understand you correctly, you're suggesting I create custom properties on the session object and then bind the relevant view component properties to the custom session properties. That would keep user preferences sticky during a session, but then they'd lose them on a new session - correct?

The app we're building will have a number of different tables that users will customize (e.g. filters, columns shown/hidden, column order, width, etc.) based on their role and location, shift, coverage area, etc. In some cases we can derive this from user data, but there's also a lot of personal preference going on and we want to provide users with a way of saving a snapshot of their customizations. We don't want them to have to go through all the clicks to restore their customizations every time they start a new session.

Maybe it's easier to save & restore the session custom properties to a DB than each of the table properties?

Correct. To make it sticky, save/load from the DB.

I'd say this is much easier and more transparent than diving into using SDK functions, especially if you get another engineer who knows nothing about the project/customization data. (That's my opinion anyways). It would also essentially 'live' in only one or two places, the session startup handler and the user id value change event, as opposed to every component that needed some sort of customization.

You could have a structure something along the lines of:

personalization
├ Tables
│ ├ TableTypeA
│ │ ├ Columns
│ │ ├ Filters
│ │ └ Rows
│ └ TableTypeB
│   └ Columns
├ Graphs
│ ├ GraphTypeA
│ └ GraphTypeB
└ ETC...

All your script would need to do is poll the DB for the provided user and transform the data into the correct structure and place that structure into the session property. Simple 3 line script for the event.

	databasePersonalizationData = system.db.runNamedQuery("GetPersonalization", {"OperatorId": operatorId})
	personalizationObject = personalizationDataToObject(databasePersonalizationData)
	session.custom.personalization = personalizationObject

with personalizationDataToObject doing all the structure transformation.

I like that approach. Time to pivot.

Thanks for the fresh angle!

1 Like

We have had some great success utilizing MongoDB for prop and template storage. We binded the props for the components we wanted to store to session custom properties and then have features added to create templates and keep user settings between sessions on perspective.