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.

Blog Post

Community Blog
5 MIN READ

Dynamic Dashboards – Passing Parameters with Repeat Arguments.

sameburn's avatar
sameburn
Contributor II
2 months ago

Introduction

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.

 

Setup: Dashboards

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.

Setup: Components

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.

 

Setup: Dynamic Dashboards Service

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:

  1. a string called NextLevelNameSuffixToAdd
  2. a Dictionary(Of String, String) called nextLevelTemplateSubstVarsToAdd.

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.

Execution

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!

Updated 4 months ago
Version 1.0
  • sameburn's avatar
    sameburn
    Contributor II

    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

  • jmohl's avatar
    jmohl
    New Contributor II

    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.

     

  • jmohl's avatar
    jmohl
    New Contributor II

    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