Unable to execute perspective functions on session property change

I am trying to execute any kind of perspective function (particularly system.perspective.openDock) on a change script for a session custom property and have noticed that I don’t have access to a perspective session in that scope.

On top of that even providing it as a parameter, it won’t allow me to execute it. Is there something I am missing here or am I running into a bug?

The script (on session.custom.debugging.showDebugContent, a boolean):

# What I actually want to run
# if currentValue.value == True:
#	system.perspective.openDock("dockedDiagnostics", sessionId=self.props.id)

# What I did in this example
system.util.getLogger("Session").info(self.props.id)
system.perspective.print("Session", sessionId=self.props.id)

The logs when I trigger this script:

The Traceback
com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last): File "", line 5, in valueChanged at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.lambda$operateOnPage$0(AbstractScriptingFunctions.java:64) at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.operateOnSession(AbstractScriptingFunctions.java:120) at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.operateOnPage(AbstractScriptingFunctions.java:47) at com.inductiveautomation.perspective.gateway.script.PerspectiveScriptingFunctions.print(PerspectiveScriptingFunctions.java:514) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.base/java.lang.reflect.Method.invoke(Unknown Source) java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: No perspective page attached to this thread.

at org.python.core.Py.JavaError(Py.java:547)

at org.python.core.Py.JavaError(Py.java:538)

at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:192)

at com.inductiveautomation.ignition.common.script.ScriptManager$ReflectedInstanceFunction.__call__(ScriptManager.java:541)

at org.python.core.PyObject.__call__(PyObject.java:400)

at org.python.pycode._pyx40.valueChanged$1(:5)

at org.python.pycode._pyx40.call_function()

at org.python.core.PyTableCode.call(PyTableCode.java:173)

at org.python.core.PyBaseCode.call(PyBaseCode.java:306)

at org.python.core.PyFunction.function___call__(PyFunction.java:474)

at org.python.core.PyFunction.__call__(PyFunction.java:469)

at org.python.core.PyFunction.__call__(PyFunction.java:464)

at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:831)

at com.inductiveautomation.ignition.common.script.ScriptManager.runFunction(ScriptManager.java:813)

at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$TrackingProjectScriptManager.runFunction(ProjectScriptLifecycle.java:806)

at com.inductiveautomation.ignition.common.script.ScriptManager$ScriptFunctionImpl.invoke(ScriptManager.java:994)

at com.inductiveautomation.ignition.gateway.project.ProjectScriptLifecycle$AutoRecompilingScriptFunction.invoke(ProjectScriptLifecycle.java:871)

at com.inductiveautomation.perspective.gateway.script.ScriptFunctionHelper.invoke(ScriptFunctionHelper.java:137)

at com.inductiveautomation.perspective.gateway.model.PropertyChangeScript.access$001(PropertyChangeScript.java:27)

at com.inductiveautomation.perspective.gateway.model.PropertyChangeScript$ScriptSequencer.run(PropertyChangeScript.java:153)

at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)

at java.base/java.util.concurrent.FutureTask.run(Unknown Source)

at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)

at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

at com.inductiveautomation.perspective.gateway.threading.BlockingWork$BlockingWorkRunnable.run(BlockingWork.java:42)

at java.base/java.lang.Thread.run(Unknown Source)

Caused by: org.python.core.PyException: java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: No perspective page attached to this thread.
... 26 common frames omitted
Caused by: java.lang.IllegalArgumentException: No perspective page attached to this thread.
at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.lambda$operateOnPage$0(AbstractScriptingFunctions.java:64)
at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.operateOnSession(AbstractScriptingFunctions.java:120)
at com.inductiveautomation.perspective.gateway.script.AbstractScriptingFunctions.operateOnPage(AbstractScriptingFunctions.java:47)
at com.inductiveautomation.perspective.gateway.script.PerspectiveScriptingFunctions.print(PerspectiveScriptingFunctions.java:514)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:190)

... 23 common frames omitted

EDIT: Note, I did also try this with just a print, no sessionId parameter, same error. I figured that since the self reference in the script is the session itself, it would have access to itself from any perspective functions.

v.8.1.15 and 8.1.14

The log’s last line appears to be complaining that it doesn’t have any perspective pageId. Both the print and openDock have the pageId as an optional parameter, but it might be required if you are calling it on a session property instead of an actual view.

You run into the same problem with running these functions in a session level message handler, you need to provide a pagId.

See if you can pass the current open page’s pageID upwards to a session variable and pass that to the print or openDock function (maybe a small script that runs on the ‘OnStartup’ event)

I don’t know the pageId thing Ryan is talking about, but as an alternative, can’t you add a property on your docked view bound to that session property, and add your change script there?

The issue here is that I need this to work across multiple pages in the same session, that’s why I had put this on a session property. I get the comment it says that if you are “targeting a different session” than you need a PageID, that makes sense, but I would feel like the session itself should have the local variables present to make session based calls, since I am not “targeting a different session”

I think I could do something like this, but I am not sure how a hidden docked view will handle its changes when there is another docked view in the way

However I still believe that workarounds aside, the session should be able to call session scoped scripts

I think this thread has the explanation you are looking for:

Okay, so looking through that I understand that it requires a pageId,

However I feel like the documentation is a little confusing here. It matches the same structure as the system.perspective.sendMessage with a required the optional session and then page, but this time its technically an optional session and page together. Its not super clear from the documentation that if you want to use the sessionId property at all (even for the session you are currently in) that you need to provide a pageId. It reads out more like you can provide session, or you can provide session and target a specific page.

That would make it more clear why you can’t make session based calls from the session object itself

Is it expected to be able to do the perspective specific functions across an entire session? or is it always intended (aside from sendMessage) to be session & page based?

The use of a perspective package function in a session property change script depends on what the function needs in order to execute. If something would act on a page, then it needs to have a pageId. If you had done something like system.perspective.closeSession(), well, that doesn’t need to know about what page you want to act on because you’re acting on the session. The .logout() function would behave similarly.

When you’re using system.perspective.sendMessage() somewhere within a page, then you already have implicit access to the properties of the page, and so the function can fall back to using those. The session property change scripts are operating independent of any page and so don’t have the fallback values you seem to be expecting.

Would be a nice feature to allow those functions executed in a session scope to just hit all pages by default, the same way that a page implicitly executes on a page as well.

Ugh. Please, no. Those of us who like to have many tabs open won’t appreciate it.

1 Like

Please don’t tell me you’re one of these kinds of people :wink:

Close your ears, then, because yes. (:

But not all the time.

And to think I looked up to you…

In reality though now that I think about it, by default probably isn’t the best answer here, however at least adding the ability would be nice. Something like sending a session with no page seems fair, since it doesn’t do anything right now on its own

At least updating the documentation to be more clear would be helpful

We could absolutely not do it by default because it would have a large impact on the way current projects work.