We are under construction!
You may experience downtime, errors or visual oddities, but we anticipate all will be resolved by the end of the day.
You may experience downtime, errors or visual oddities, but we anticipate all will be resolved by the end of the day.
This is an introduction to the power of Repeat Arguments in OneStream Workspaces for v8+. In this blog we will walk you through a simple example, creating 17 repeat arguments based on the Dimensions in OneStream (Entity / Consolidation / Scenario / Time / View / Account / Flow / Origin / IC / UD1 - UD8) with only a handful of components and almost no code.
NOTE: this blog is not an exhaustive account of how Dynamic Dashboard Repeat Arguments work, but rather an application of these concepts using minimal code. You might want to familiarize yourself with the concepts first, by reading the Embedded Dynamic Repeater Dashboard section of the OneStream Design & Reference guide and setting up an example.
Before version 8, we would not have been able to achieve this without creating 17 dashboards, 17 buttons, 17 labels and 17 parameters. To Dashboard developers, the power of Dynamic Dashboards is kind of a big deal !
Let’s have a quick look at the output before we dive into the setup.
This example has been created in its own workspace called WsMemberFilterBuilder. We have 3 dashboards, 1 button and 1 label.
Let’s look at the dashboard from the bottom up. 2_Layout Content is our lowest level dashboard; it has been set with Layout Type Grid and has 1 row and 2 columns.
This dashboard also contains our button and our label components.
Next, we add the embedded version of this dashboard to 1_Content.
We set the Dashboard Type here to be Embedded Dynamic. This ensures that we can interact with it in the Dynamic Dashboard Service (more to come on that later)…
Finally, we add the embedded version of this dashboard to 0_Frame.
Let’s now look at the two components. Firstly we are using a Button with Button Type Member Filter Builder. This is available from OneStream v8.1.
On the button we place an XFBR which will return the Dimension Token for each repeat argument. For example, passing "Entity" will return "E#", "Scenario" will return "S#", etc.
We also have a Template Parameter that only applies to Repeater Dashboards, e.g. ~!Dim!~. It will be populated at each iteration with the dimension name.
What we are passing into the Bound Parameter of this stored component is the most important thing. Here we add a “prm” prefix to each repeat argument to give us a unique parameter name for each repeat argument e.g. Entity, Consolidation, Scenario, Time, View, Account, Flow, Origin, IC, UD1, UD2, UD3, UD4, UD5, UD6, UD7 and UD8. This gives us a different parameter result for each dynamic component. We are also refreshing the Embedded Dynamic Repeater dashboard in our interface action, so that interaction with this button will trigger a global refresh.
Next, let’s look at our label component, which will contain the result of each button click. Here we are catching the result of a Parameter in the traditional way, enclosing the parameter name in pipes and exclamation marks; but we are also using the syntax we saw on the button component, pointing to a parameter called |!prm~!Dim!~!|.
Before we look at the code, let’s pause and review the dashboard in Design Mode. Here you can see that for each repeated component, we have a suffix of _dynamic_~!Dim!~ : _dynamic_Entity, _dynamic_Consolidation, etc. OneStream is creating dynamic components for us based on our repeat arguments.
Note how we still have to follow traditional Dashboard rules: components within an individual workspace must be uniquely named.
We then end up with a dashboard containing 17 dashboards, 17 buttons, 17 labels and 17 parameters, but created from (a) our stored components and (b) our repeat arguments.
In order to make this work, we will need a bit of code, which we will place in a Workspace Assembly. If you are not familiar with Assemblies, you can find more information on them here and here in the OneStream Design & Reference guide.
We are also using something called a Service Factory (new to v8) and a Dynamic Dashboards Service. Again, you can read all about them in the OneStream Design & Reference guide here and here.
So how does all this work? I’ve talked a lot so far about Repeat Arguments, but how do we create them?
In principle, it is the same concept whether we setup a code or a no-code example. Let’s look at the no-code example first, like the example I shared earlier here from the OneStream Design & Reference guide. If we were to set up our system without code, we would create a list of Template Name Suffixes for our component (e.g. Entity,Consolidation, etc) and a Dictionary of NameValuePairs (e.g. Dim=[Entity], or Dim=[Consolidation], etc). Remember how we are using a variable called Dim to use our repeat arguments in component, with the Template Parameter ~!Dim!~.
We can do the same thing using the GetDynamicComponentsForDynamicDashboard function. On line 52 we check for the embedded repeater dashboard e.g. 1_Content.
Then we declare a placeholder for our List(Of WsDynamicComponentRepeatArgs) on line 55 and derive our arguments list on row 56.
I am deriving mine as Comma Separated Values from a function, but you can derive the arguments any way that you like. In my case I am executing the equivalent of this list of dimensions e.g.
Next, we need to populate our Repeat Arguments list. To do so, we loop on our arguments list and inside the loop we create a new dictionary where we use the Key Dim and the value from our loop (e.g. Entity) on line 62.
Then on line 64 we populate our repeatArgsList. Here we create the WsDynamicComponentRepeatArgs object with two variables:
If this looks familiar, it's because it is exactly the same variables we saw in the no-code example.
So NextLevelNameSuffixToAdd = “Template Name Suffix” and nextLevelTemplateSubstVarsToAdd = “Template Parameter Values”.
That’s right, we are creating the same repeat arguments in both examples.
Similarities don’t end there: with the no-code example (using Embedded Dynamic Repeater Dashboard Type), we are returning a collection of Component Template Repeat Items.
In code, we are returning the object WsDynamicComponentCollection, which is effectively the same thing.
Now that we have seen the setup, let’s look at what we have created. By harnessing the power of dynamic dashboards, we have generated 17 versions of each component. This means that I can select E# and an Entity Selection e.g.
And catch the result in a label…
But it gets better than that, because each button and label now work as pairs in our repeat arguments list, since each one uses a unique parameter (that we created dynamically)
Our repeat arguments interact with the Dashboard Service, which drives our logic, to generate many dynamic components for us. We could make this setup even more dynamic by allowing interaction with the data that creates arguments, for example through the use of a Parameter listing the dimensions. This means you can still create powerful dashboards, but with fewer components and just a bit of dashboard wizardry.
Once you understand how to harness this type of paradigm, you'll quickly find a number of possible applications for it, making your Dashboard development experience much more productive and, hopefully, more fun!
Blogs from Experts on OneStream
Very well explained.
good job!
Hi jmohl
If you hard code a value on the text property of the button does it actually run as expected e.g repeating components for each set of your template parameters?
If not, maybe have a look at the no code example in the design & reference guide first (link in blog above) to check your setup
Looks like something may be awry in your setup based on that error rather than how you are retrieving the Dimension NameValuePair in your XFBR
Hope this helps
Sam
Thanks, sameburn. I was able to get a similar result to what your blog showed. My issue ended up being specific to the "Default Member Filter" setting. It was still looking up the literal template parameter as a function argument (unsuccessfully) in the dictionary, so I removed the XFBR call and it ran fine. I am still unsure why an identical XFBR call works in the "Text" setting but not the "Default Member Filter" setting (in version 8.2). However, the end result is relatively similar, and this gives me a working example to reference for future builds.
Thanks, this is helpful. When trying to replicate your results, I am having trouble passing the ~!Dim!~ template parameter into my XFBR to retrieve the dimension token, as OneStream is interpreting the template parameter literally. Will args.NameValuePairs.XfGetValue("Key") process template variables, or is an alternate method required to pass the template parameter into an XFBR? Maybe there is something else I am missing (e.g., Template Parameter Values).
Imports System
Imports System.Collections.Generic
Imports System.Data
Imports System.Data.Common
Imports System.Globalization
Imports System.IO
Imports System.Linq
Imports Microsoft.VisualBasic
Imports OneStream.Finance.Database
Imports OneStream.Finance.Engine
Imports OneStream.Shared.Common
Imports OneStream.Shared.Database
Imports OneStream.Shared.Engine
Imports OneStream.Shared.Wcf
Imports OneStream.Stage.Database
Imports OneStream.Stage.Engine
Imports OneStreamWorkspacesApi
Imports OneStreamWorkspacesApi.V800
Imports OneStreamWorkspacesApi.V820
Namespace Workspace.__WsNamespacePrefix.__WsAssemblyName
Public Class MyXFBRs
Implements IWsasXFBRStringV800
Public Function GetXFBRString(ByVal si As SessionInfo, ByVal globals As BRGlobals, ByVal workspace As DashboardWorkspace, _
ByVal args As DashboardStringFunctionArgs) As String Implements IWsasXFBRStringV800.GetXFBRString
Try
If (globals IsNot Nothing) AndAlso (workspace IsNot Nothing) AndAlso (args IsNot Nothing) Then
If args.FunctionName.XFEqualsIgnoreCase("GetDimToken") Then
Dim dimension As String = args.NameValuePairs.XFGetValue("Dimension")
'Create dictionary to store all dim type Ids
Dim dimDictionary As New Dictionary(Of String, String)
'Add each dimension to the dictionary
dimDictionary.Add("Entity", "E#")
dimDictionary.Add("Scenario", "S#")
dimDictionary.Add("Time", "T#")
dimDictionary.Add("Consolidation", "C#")
dimDictionary.Add("Parent", "P#")
dimDictionary.Add("Account", "A#")
dimDictionary.Add("Flow", "F#")
dimDictionary.Add("Origin", "O#")
dimDictionary.Add("View", "V#")
dimDictionary.Add("Intercompany", "IC#")
dimDictionary.Add("UD1", "U1#")
dimDictionary.Add("UD2", "U2#")
dimDictionary.Add("UD3", "U3#")
dimDictionary.Add("UD4", "U4#")
dimDictionary.Add("UD5", "U5#")
dimDictionary.Add("UD6", "U6#")
dimDictionary.Add("UD7", "U7#")
dimDictionary.Add("UD8", "U8#")
'Lookup the selected dimension's value in the dictionary
Dim returnDimToken As String = dimDictionary.Item(Dimension)
'Return the selection if a value exists
If Not returnDimToken Is Nothing Then
Return returnDimToken
Else
Return Nothing
End If
Else
Return Nothing
End If
Else
Return Nothing
End If
Catch ex As Exception
Throw ErrorHandler.LogWrite(si, New XFException(si, ex))
End Try
End Function
End Class
End Namespace