Announcement
ABCFeatured Content
Recent Activity
How to report on Entity Security changes
Hi all! Anyone know of the best way to report on security changes made to an entity? I can't seem to figure out the right table or the right member property that shows the security updates made to a member so any direction would be greatly appreciated! Full disclosure - also have a OS support ticket out there and patiently waiting :)NicoleBruno2 hours agoValued Contributor8Views0likes1CommentLIM: Example of Register Field Population from a Custom XFC Table
DISCLAIMER: It should be noted that the focus of this technical guide is to provide general information, considerations, and guidelines for an identified topic. It is NOT to be interpreted as the ONLY approach nor a guarantee that there will not be any issues encountered by using this approach as a customer’s requirements or application configuration may render this guidance as not applicable. In addition, statements that “we believe” and similar statements reflect our beliefs and opinions on the relevant subject. These statements are based upon information available to us as of the date of this article, and while we believe such information forms a reasonable basis for such statements, such information may be limited or incomplete, and our statements should not be read to indicate that we have conducted an exhaustive inquiry into, or review of, all potentially available relevant information. OneStream does not warrant as to the accuracy of this guidance, which is provided on an as-is basis. Any forward-looking statements contained herein are based on information available at the time those statements are made and/or good faith beliefs and assumptions as of that time with respect to future events and are subject to risks and uncertainties that could cause actual performance or results to differ materially from those expressed in or suggested by the forward-looking statements. Considering these risks and uncertainties, the forward-looking events and circumstances discussed in this guide may not occur and actual results could differ materially from those anticipated or implied in the forward-looking statements. VERSION: PV900 SV200 PLATFORM: 9.2.0 USE CASE: your customer wants a standard repository to store supplementary information to be used to populate certain Register Fields. In this example, a list of job codes and its descriptions is handled as a single custom table within OneStream. Every time a user selects a job code or inputs a job code in the Register, the expectation is the Job Code Description register field is populated with the description that is from this custom table. PURPOSE: to provide you with an example that you can leverage as part of your build, whether you decide to incorporate this to existing input options (Register, Spreadsheet, Import File, Connector). ASSUMPTIONS: The relevant Register Fields have already been created. In my example, I have JobCode and JobCodeDescription register fields. The relevant XFC custom table is created, which for my example is two columns: one that is JobCode and the second column that is JobCodeDesc. A simple dashboard that allows the Administrator or select users to update the XFC table. This section covers the following files/business rules with examples of the logic: A) WsComponentService: ImportRegisterData B) WorkforcePlanningImportRegisterConnector# dashboard extender business rule (to handle connector) C) WsDynamicGridService D) WsTableViewService A) WsComponentService: ImportRegisterData (to handle importing files) Example logic to handle populating the JobCodeDescription for WsComponentService: Example logic of populateJobCodeDesc method: private static void populateJobCodeDesc(SessionInfo si, BRGlobals brGlobals, DashboardWorkspace workspace, DashboardExtenderArgs args, XFSelectionChangedTaskResult taskResult) { int rowNumber = 0; var errorList = new List<string>(); var invalidJobCodes = new List<string>(); try { DataTable registerRows = GetRegisterRows(si); foreach (DataRow regRow in registerRows.Rows) { rowNumber++; string jcName = regRow["JobCode"].ToString(); string empId = regRow["EmployeeID"].ToString(); string jcDescription; try { string sql = @$" SELECT JobCodeDesc FROM dbo.XFC_JobCodes WHERE JobCode = '{SqlStringHelper.EscapeSqlString(jcName)}' "; var dt = BRApi.Database.ExecuteSql(BRApi.Database.CreateApplicationDbConnInfo(si),sql,false); if (dt != null && dt.Rows.Count > 0) { jcDescription = dt.Rows[0]["JobCodeDesc"].ToString(); } else { // No rows returned = invalid job code jcDescription = "INVALID JOB CODE"; invalidJobCodes.Add(jcName); errorList.Add($"Row {rowNumber}, EmployeeID {empId}, JobCode '{jcName}': INVALID JOB CODE"); } } catch (Exception innerEx) { // Exception = invalid job code jcDescription = "INVALID JOB CODE"; invalidJobCodes.Add(jcName); errorList.Add($"Row {rowNumber}, EmployeeID {empId}, JobCode '{jcName}': {innerEx.Message}"); } // Always update the staging table with the resolved description (valid or INVALID) string updateSql = $@" UPDATE [limb2].XFW_LIM_StageRegister SET JobCodeDescription = '{SqlStringHelper.EscapeSqlString(jcDescription)}' WHERE JobCode = '{SqlStringHelper.EscapeSqlString(jcName)}'; "; BRApi.Database.ExecuteSql(BRApi.Database.CreateApplicationDbConnInfo(si), updateSql, false); } if (errorList.Count > 0) { string errorMessage = "Some JobCodes were marked as INVALID:" + Environment.NewLine + string.Join(Environment.NewLine, errorList); throw new XFException(si, new Exception(errorMessage)); } } catch (Exception ex) { throw new XFException(si, new Exception("Unexpected error occurred: " + ex.Message, ex)); } } IMPORTANT NOTE: this example assumes that this is the only customization needed to the WsComponentService file that calls upon the GetRegisterRows helper method. If you have more customizations, then you will need to assess how many of these customizations rely on fetching the register rows. You want to consider fetching these register rows once and reuse it instead of having each method (which corresponds to a different customization) call it; this is to optimize performance and processing. Below is a sample code equivalent, which pertains to SV200 in which the table names have changed from PLN to LIM. private static DataTable GetRegisterRows(SessionInfo si) { try { var sql = new System.Text.StringBuilder(); sql.AppendLine($@" SELECT [limb2].XFW_LIM_StageRegister.EmployeeID, [limb2].XFW_LIM_StageRegister.Instance, [limb2].XFW_LIM_StageRegister.LastName, [limb2].XFW_LIM_StageRegister.FirstName, [limb2].XFW_LIM_StageRegister.Status, [limb2].XFW_LIM_StageRegister.HireDate, [limb2].XFW_LIM_StageRegister.HirePeriod, [limb2].XFW_LIM_StageRegister.TermDate, [limb2].XFW_LIM_StageRegister.TermPeriod, [limb2].XFW_LIM_StageRegister.Salary, [limb2].XFW_LIM_StageRegister.SalaryOverride, [limb2].XFW_LIM_StageRegister.Entity, [limb2].XFW_LIM_StageRegister.CostCenter, [limb2].XFW_LIM_StageRegister.CostCenterDescription, [limb2].XFW_LIM_StageRegister.JobCode, [limb2].XFW_LIM_StageRegister.JobCodeDescription, [limb2].XFW_LIM_StageRegister.FTE, [limb2].XFW_LIM_StageRegister.HireYear, [limb2].XFW_LIM_StageRegister.TermYear FROM [limb2].XFW_LIM_StageRegister "); using (DbConnInfo dbConnApp = BRApi.Database.CreateApplicationDbConnInfo(si)) { DataTable dt = BRApi.Database.ExecuteSql(dbConnApp, sql.ToString(), false); return dt; } } catch (Exception ex) { throw ErrorHandler.LogWrite(si, new XFException(si, ex)); } } NOTE: if you plan to use StringBuilder() instead of declaring the variable as a string, you will need to import System.Text into the file. StringBuilder() is a method group so it will also have to be referenced as sql.ToString() as part of the BRApi.Database.ExecuteSql line. Example of what I mean by “import” below. B) WorkforcePlanningImportRegisterConnector#*: ImportRegisterData (to handle connector) *Business rule name may be different depending on the LIM selection and number of workspaces created. Before: this is before any customizations. This is what your ImportRegisterData should start out as. After: example of how it could be re-arranged to handle customizations IMPORTANT NOTE: the above assumes this is the only customization needed to the Import Connector. If you have more customizations, then you will need to assess how many of these customizations rely on fetching the register rows. You want to consider fetching these register rows once and reuse it instead of having each method (which corresponds to a different customization) call it; this is to optimize performance and processing. Example below of what this ImportRegisterData could look like if you have multiple customizations that rely on fetching register rows. Anyways, back to assuming that there are multiple customizations, the below is an example to handle populating the job code description for WorkforcePlanningImportRegisterConnector#*: private static void populateJobCodeDesc(SessionInfo si, BRGlobals brGlobals, DashboardExtenderArgs args, DataTable registerRows) { var errorList = new List<string>(); var invalidJobCodes = new HashSet<string>(StringComparer.OrdinalIgnoreCase); try { using var dbConn = BRApi.Database.CreateApplicationDbConnInfo(si); int rowNumber = 0; foreach (DataRow regRow in registerRows.Rows) { rowNumber++; string jcName = regRow["JobCode"].ToString(); string empId = regRow["EmployeeID"].ToString(); string jcDescription = ResolveJobCodeDescription(si, dbConn, jcName); if (jcDescription == "INVALID JOB CODE") { invalidJobCodes.Add(jcName); errorList.Add($"Row {rowNumber}, EmployeeID {empId}, JobCode '{jcName}': INVALID JOB CODE"); } // Update staging table with resolved description string updateSql = $@" UPDATE [limb2].XFW_LIM_StageRegister SET JobCodeDescription = '{SqlStringHelper.EscapeSqlString(jcDescription)}' WHERE JobCode = '{SqlStringHelper.EscapeSqlString(jcName)}'; "; BRApi.Database.ExecuteSql(dbConn, updateSql, false); } if (errorList.Count > 0) { string errorMessage = "Some JobCodes were marked as INVALID:" + Environment.NewLine + string.Join(Environment.NewLine, errorList); throw new XFException(si, new Exception(errorMessage)); } } catch (Exception ex) { throw new XFException(si, new Exception("Unexpected error occurred: " + ex.Message, ex)); } } NOTE: if you plan to use StringBuilder() instead of declaring the variable as a string in your WorkforcePlanningImportRegisterConnector#* rule, you will need to import System.Text into the business rule. StringBuilder() is a method group so it will also have to be referenced as sql.ToString() as part of the BRApi.Database.ExecuteSql line. C) WsDynamicGridService: Register Below is an example of how I arranged my logic within my BeforeEvent. The below is an example of the logic associated with populateJobCodeDesc. private static void populateJobCodeDesc(SessionInfo si, DashboardDynamicGridArgs args) { List<string> invalidJobCodes = new List<string>(); if (args.SaveDataArgs != null) { foreach (XFEditedDataRow row in args.SaveDataArgs.EditedDataRows) { //if (row.InsertUpdateOrDelete != DbInsUpdateDelType.Update) continue; if (row.InsertUpdateOrDelete != DbInsUpdateDelType.Insert && row.InsertUpdateOrDelete != DbInsUpdateDelType.Update) continue; string jcName = row.ModifiedDataRow.Items["JobCode"].ToString(); string empId = row.ModifiedDataRow.Items["EmployeeID"].ToString(); string jcDescription = null; try { string sql = @$" SELECT JobCodeDesc FROM dbo.XFC_JobCodes WHERE JobCode = '{jcName}' "; var dt = BRApi.Database.ExecuteSql(BRApi.Database.CreateApplicationDbConnInfo(si), sql, false); if (dt != null && dt.Rows.Count > 0) { jcDescription = dt.Rows[0]["JobCodeDesc"].ToString(); row.ModifiedDataRow.Items["JobCodeDescription"] = jcDescription; } else { // No rows returned = invalid job code invalidJobCodes.Add($"EmployeeID {empId}, JobCode {jcName}"); row.ModifiedDataRow.Items["JobCodeDescription"] = null; } } catch { // Any exception = treat as invalid invalidJobCodes.Add($"EmployeeID {empId}, JobCode {jcName}"); row.ModifiedDataRow.Items["JobCodeDescription"] = null; } } } if (invalidJobCodes.Count > 0) { string errorMessage = "Invalid job codes detected:" + Environment.NewLine + string.Join(Environment.NewLine, invalidJobCodes); throw new XFException(si, new Exception(errorMessage)); } } D) WsTableViewService: Spreadsheet Below is an example of how I arranged my logic within my BeforeEvent. The below is an example of the populateJobCodeDesc logic. Because I am pulling from a SQL and then processing each table view-related row one by one, it is more performant to have the XFC table cached once in memory. It is also important to only process rows with the valid information so I have a section to identify which rows to skip. private static void populateJobCodeDesc(SessionInfo si, BRGlobals globals, DashboardWorkspace workspace, TableView tableView) { // Get cached job codes as dictionary string cacheKey = $"CachedResult_{workspace.WorkspaceID}"; var cachedResult = globals.GetObject(cacheKey) as Dictionary<string, string>; Dictionary<string, string> jobCodeMap; if (cachedResult != null) { jobCodeMap = cachedResult; } else { string sql = @"SELECT JobCode, JobCodeDesc FROM dbo.XFC_JobCodes"; var connInfo = BRApi.Database.CreateApplicationDbConnInfo(si); var dt = BRApi.Database.ExecuteSql(connInfo, sql, false); jobCodeMap = dt.AsEnumerable() .ToDictionary(r => r.Field<string>("JobCode"), r => r.Field<string>("JobCodeDesc")); globals.SetObject(cacheKey, jobCodeMap); } var invalidJobCodes = new List<string>(); foreach (var row in tableView.Rows) { //identify rows to skip if (row.IsHeader) continue; var originalStatus = row.Items["Status"].Value?.ToString(); if (string.IsNullOrWhiteSpace(originalStatus)) continue; if (!row.Items["JobCode"].IsDirty()) continue; string jcName = row.Items["JobCode"].Value?.ToString(); string empId = row.Items["EmployeeID"].Value?.ToString(); if (jobCodeMap.TryGetValue(jcName, out var jcDescription)) { row.Items["JobCodeDescription"].Value = jcDescription; } else { invalidJobCodes.Add($"EmployeeID {empId}, JobCode {jcName}"); row.Items["JobCodeDescription"].Value = null; } } if (invalidJobCodes.Count > 0) { string errorMessage = "Invalid job codes detected:" + Environment.NewLine + string.Join(Environment.NewLine, invalidJobCodes); throw new XFException(si, new Exception(errorMessage)); } }jzhang7 hours agoOneStream Employee20Views0likes0CommentsHow to Recall Another Business Rule into a Different Business Rule
Hello, Trying to build a business rule to send an email once a button is pressed. The business rule that I'm trying to use is encrypted and I cannot get into it so I was trying to figure out a way to recall that business rule to check if a Lease change status to submitted. I read something about Referenced Assemblies, but I'm not fully sure if that's right or what I even code it as in my current business rule. This is the BR I'm trying to call into my current rule Thanks, WillWillVitale10 hours agoContributor II14Views0likes1CommentFloor's Lava—Time to Branch Out: Building Your Tree
Welcome to the blog! In today's post, we're diving deep into the world of hierarchical data visualization—Mission Tree-Ting style. If you’ve ever needed to see your organization’s structure at a glance or wanted a smart, efficient way to visualize complex relationships like employee-manager hierarchies, you're in the right place. Objective: We’ll show you how to build a robust tree structure using VB.NET that transforms raw data into an intuitive, expandable TreeView. By leveraging a custom DataAdapter and binding it to a dashboard component, you'll learn to effortlessly bring your data to life—no messy code left behind! Use Case: Imagine managing an organizational chart where each node represents an employee and their reporting relationships. Whether you're in HR, IT, or any department that thrives on clear, visual insights, this solution provides a reusable method to: Parse and filter your data via dynamic SQL queries. Construct a hierarchical tree with unique nodes and child collections. Seamlessly embed this TreeView into your OneStream dashboard, allowing for interactive exploration of your organizational structure. So, grab your coding toolkit and get ready to transform your data into a dynamic tree that not only meets everyday business needs but also adds a touch of Mission Impossible flair to your projects. Let's get started on turning the impossible into possible—tree style! Although this example demonstrates a use case from People Planning, the same approach can be applied to any dataset with a Parent/Child hierarchy — such as product hierarchies, organizational structures, Capital Planning or cost centers. Note: The method described in this blog is one of many possible ways to visualize and manage hierarchical data in OneStream. Depending on your business requirements, alternate implementations may better suit your needs. Please refer below Blogs related to the Tree View Introduction To Tree Views | OneStream Community How to build a Multi-Select TreeView | OneStream Community Overview What is Tree View? The TreeView is primarily used for intuitive navigation and visualization of hierarchical relationships. It helps users quickly identify structural paths and validate lineage, making it useful for both data review and administrative control. Generating Tree View Our function, GetTreeViewEmpMgr, performs the following steps: Constructs an SQL Query: The query selects employee data along with manager names. A NULL value for the manager indicates that the employee is at the top level (a root node). Executes the Query and Retrieves a DataTable: Using a database connection, the function executes the SQL query and stores the result in a DataTable named "Employee". Builds the Tree Structure: The function uses custom classes—XFTreeItem and XFTreeItemCollection—along with a dictionary called nodesByName to construct the tree: XFTreeItem represents each node in the tree. It holds properties such as the display text (HeaderText), state information (bold, selected, expanded), visual properties (colors and images), and a list of child nodes. XFTreeItemCollection is a container for the tree’s nodes (typically the root nodes) and provides methods for creating a DataSet from the entire tree. The nodesByName dictionary is used to quickly look up and manage nodes by their unique identifier (in this case, by name), ensuring that each node is only created once and can easily have children added later. Returns the Tree as a DataSet: Finally, the tree is converted into a DataSet using treeItems.CreateDataSet(si) and returned for UI rendering. Constructs an SQL Query The data is retrieved from the XFW_PLP_Register (People Planning Register) based on the current Workflow Profile, Time, and Scenario — all passed as parameters to ensure contextual relevance. Select CASE WHEN M.RegisterID = E.RegisterID THEN NULL Else Concat(M.FirstName,' ',M.LastName) End as 'Parent' , Concat(E.FirstName,' ',E.LastName) as 'Child' FROM XFW_PLP_Register E LEFT JOIN XFW_PLP_Register M ON E.Code11 = M.RegisterID Where E.Status <> 'NewHire' And E.WFScenarioName = '" & WFScenarioName & "' And E.WFTimeName = '" & WFTimeName & "' And E.WFProfileName = '" & WFProfileName & "' And (M.WFScenarioName = '" & WFScenarioName & "' And M.WFTimeName = '" & WFTimeName & "' And M.WFProfileName = '" & WFProfileName & "' OR M.RegisterID IS NULL) For each employee, we extract the First Name and Last Name, along with their manager’s corresponding First Name and Last Name. In this configuration, the manager's Employee ID is stored in the Code11 field. By performing a self-join on the XFW_PLP_Register table using this reference, we can resolve and display the appropriate manager name for every employee. Emphasis on Key Components XFTreeItem The XFTreeItem class is the building block of our tree structure. Each XFTreeItem represents a node that can have: HeaderText: The display text for the node. Visual Properties: Such as text color, images (using properties like imageSource and imageName), and formatting (bold, enabled, selected, expanded). Children Collection: A list that holds any child nodes, making the node hierarchical. Additional Data: You can attach other metadata as needed, making XFTreeItem flexible for various applications. ' Common visual properties. Dim textColour As String = XFColors.Black.Name Dim imageSource As String = XFImageFileSourceType.ClientImage Dim imageName As String = XFClientImageTypes.StatusGrayBall.Name Dim isBold As Boolean = False Dim isEnabled As Boolean = True Dim isSelected As Boolean = False Dim isExpanded As Boolean = False In our code, we create new XFTreeItem objects for every child and parent as needed, ensuring that each node correctly reflects its employee or manager data. Initializing the node with default properties ' Create a node for the child. Dim childNode As New XFTreeItem(childName, childName, textColour, isBold, isEnabled, isSelected, isExpanded, imageSource, imageName, childName, Nothing) updating the parent node properties ' Create a new parent node if it doesn't exist. parentNode = New XFTreeItem(parentName, parentName, textColour, isBold, isEnabled, isSelected, True, imageSource, imageName, parentName, Nothing) 'Updating the properties of parent node If parentNode.Children Is Nothing Then parentNode.IsBold = True parentNode.IsExpanded =True parentNode.Children = New List(Of XFTreeItem) End If parentNode.HeaderText = parentName+ " ("+(parentNode.Children.Count+1).ToString+")" 'Adding the child node to the parent's children collection parentNode.Children.Add(childNode) XFTreeItemCollection The XFTreeItemCollection class serves as the container for the entire tree. It typically holds the collection of root nodes and offers helper functions to manipulate or output the tree. In our function, after building the tree, we call: Return treeItems.CreateDataSet(si) This method converts the tree structure into a DataSet, making it easier to bind to UI controls like a TreeView. By using a dedicated collection class, we encapsulate all tree-related functionality, keeping the code modular and maintainable. Item Table holds the detailed data for each node. Relationship Table captures how these nodes relate to each other (i.e., which node is the parent of which). Note: These are random names not any Org data nodesByName Dictionary The nodesByName dictionary is a crucial element in building the tree. Its main purposes are: Ensuring Uniqueness: It prevents duplicate creation of nodes by keeping track of every node by its unique name (or identifier). Facilitating Lookup: When processing a new row, the dictionary is quickly checked to see if a node already exists for the given parent or child. This way, if a parent node is referenced later, it is easily found and updated. Simplifying Tree Building: By centralizing node management, it simplifies the process of attaching children to the correct parent nodes. ' If there is a parent, check if the parent node already exists. Dim parentNode As XFTreeItem = Nothing If nodesByName.ContainsKey(parentName) Then parentNode = nodesByName(parentName) BRApi.ErrorLog.LogMessage(si,"Parent is there: "+parentNode.HeaderText) Else ' Parent does not exist yet; create it as a root node. parentNode = New XFTreeItem(parentName, parentName, textColour, isBold, isEnabled, isSelected, True, imageSource, imageName, parentName,Nothing) treeItems.TreeItems.Add(parentNode) nodesByName(parentName) = parentNode End If This approach avoids the overhead of scanning a list for a matching node each time and guarantees that each node appears only once in the tree. The Complete Code Below is the complete VB.NET code for the GetTreeViewEmpMgr function with detailed comments explaining the use of XFTreeItem, XFTreeItemCollection, and nodesByName. Public Function GetTreeViewEmpMgr(ByVal si As SessionInfo, ByVal args As DashboardDataSetArgs) As DataSet Try Dim sql As New Text.StringBuilder() Dim WFProfileName As String = args.NameValuePairs.XFGetValue("WFProfileName") Dim WFScenarioName As String = args.NameValuePairs.XFGetValue("WFScenarioName") Dim WFTimeName As String = args.NameValuePairs.XFGetValue("WFTimeName") sql.append("Select") sql.Append(" CASE WHEN M.RegisterID = E.RegisterID THEN NULL ") sql.append(" Else Concat(M.FirstName,' ',M.LastName) End as 'Parent'") sql.append(", Concat(E.FirstName,' ',E.LastName) as 'Child'") sql.append(" FROM XFW_PLP_Register E ") sql.append("LEFT JOIN XFW_PLP_Register M ON E.Code11 = M.RegisterID ") sql.append("Where ") sql.Append("E.Status <> 'NewHire' And") sql.append(" E.WFScenarioName = '" & WFScenarioName & "' And E.WFTimeName = '" & WFTimeName & "' And E.WFProfileName = '" & WFProfileName & "' And ") sql.append(" (M.WFScenarioName = '" & WFScenarioName & "' And M.WFTimeName = '" & WFTimeName & "' And M.WFProfileName = '" & WFProfileName & "' OR M.RegisterID IS NULL)") BRApi.ErrorLog.LogMessage(si,sql.ToString) Dim dt As DataTable 'Execute the query Using dbConnApp As DbConnInfo = BRApi.Database.CreateApplicationDbConnInfo(si) dt = BRApi.Database.ExecuteSql(dbConnApp, sql.ToString(), False) dt.TableName = "Employee" End Using ' Create the main tree collection. Dim treeItems As New XFTreeItemCollection ' Dictionary to keep track of nodes by name. Dim nodesByName As New Dictionary(Of String, XFTreeItem)() ' Common visual properties. Dim textColour As String = XFColors.Black.Name Dim imageSource As String = XFImageFileSourceType.ClientImage Dim imageName As String = XFClientImageTypes.StatusGrayBall.Name Dim isBold As Boolean = False Dim isEnabled As Boolean = True Dim isSelected As Boolean = False Dim isExpanded As Boolean = False #Region "Buid Tree" ' Process each row from the DataTable. Dim i As Integer = 0 For Each row As DataRow In dt.Rows Dim parentName As String = "" If Not IsDBNull(row("Parent")) Then parentName = row("Parent").ToString().Trim() End If Dim childName As String = row("Child").ToString().Trim() ' Create a node for the child. Dim childNode As New XFTreeItem(childName, childName, textColour, isBold, isEnabled, isSelected, isExpanded, imageSource, imageName, childName, Nothing) BRApi.ErrorLog.LogMessage(si,"Adding to Child Node: "+childNode.HeaderText) If String.IsNullOrEmpty(parentName) Then ' If there is no parent, then this is a root node. treeItems.TreeItems.Add(childNode) nodesByName(childName) = childNode Else ' If there is a parent, check if the parent node already exists. Dim parentNode As XFTreeItem = Nothing If nodesByName.ContainsKey(parentName) Then parentNode = nodesByName(parentName) Else ' Parent does not exist yet; create it as a root node. parentNode = New XFTreeItem(parentName, parentName, textColour, isBold, isEnabled, isSelected, True, imageSource, imageName, parentName,Nothing) treeItems.TreeItems.Add(parentNode) nodesByName(parentName) = parentNode End If ' Add the child node to the parent's children collection. If parentNode.Children Is Nothing Then parentNode.IsBold = True parentNode.IsExpanded =True parentNode.Children = New List(Of XFTreeItem) End If parentNode.HeaderText = parentName+ " ("+(parentNode.Children.Count+1).ToString+")" parentNode.Children.Add(childNode) ' Also add the child node to the dictionary. If Not nodesByName.ContainsKey(childName) Then nodesByName.Add(childName, childNode) End If End If Next #End Region Return treeItems.CreateDataSet(si) Catch ex As Exception Throw ErrorHandler.LogWrite(si, New XFException(si, ex)) End Try End Function How to Call the Employee-Manager Tree Code via DataAdapter The code in the GetTreeViewEmpMgr function is designed to be invoked from the DataAdapter layer using a parameterized string. This allows you to easily pass in filtering values and trigger the building of the hierarchical tree structure based on the specified employee-manager relationships. {TreeView}{EmpMgr}{WFProfileName= [PLP_US.PeoplePlanning],WFScenarioName=|WFScenario|,WFTimeName=|WFTime|} Attach the DataAdapter to the TreeView and Embed it into the Dashboard: Attach and Bind: Create a TreeView component in your application and bind the DataSet produced by the GetTreeViewEmpMgr function directly to this TreeView. This binding ensures that the hierarchical data is rendered correctly in the TreeView control Embed into the Dashboard: Once the TreeView is populated, embed it into your dashboard. This integration allows end-users to interact with and explore the employee-manager hierarchy in a visually intuitive manner. Together, these steps transform raw data into a dynamic, interactive component that sits proudly on your dashboard—just like a successful mission from Mission Impossible. Your organizational structure is now displayed in a neat, expandable tree format, and your mission is accomplished! For Complete Code and Detailed Setup Instructions, Please Refer to the GitHub Repository: https://github.com/Sudha8990AI/TreeViewSudhakar12 hours agoNew Contributor III421Views2likes1CommentMergeAndCenterColumnHeaders in Cube Views
Hi, Anyone on Version 8 who's actually tried to use this 'enhancement' feature in Cube Views? We requested this on Ideastream a long time ago with a lot of support. I can see this command is now available in the header format dialog; however, it would appear that the only thing it does is change the header from left justified to center view. There does not seem to be a way to actually 'merge' column headers, despite the fact that it's called "MergeAndCenter". Even the system documentation appears to confirm this behavior. Someone please tell me I'm missing something here and it actually is possible to merge column headers, because we really really wanted this feature. Thanks726Views2likes11CommentsGenesis Home Page, Navigation to File or Website
Hello everyone! Not sure if this is the right forum or not, but I am creating a very simple home page using the Genesis Home Page. Creating buttons to go to Dashboards or Workflows was not an issue, but I cannot figure out how to get the buttons to work to navigate to a document or website. Below is how I was attempting to do it. If anyone has any advice on what I am missing, it would be greatly appreciated, thank you.KevinEvange1 day agoNew Contributor II21Views0likes0CommentsCourse Announcement: Workforce Planning Express
1 MIN READ Course Description: This module provides an end-to-end walkthrough of Workforce Planning Express, from initial configuration and driver setup through workforce data integration, calculation, analysis, and loading results into the financial planning cube. Learners will see how Workforce Planning Express supports accurate, repeatable workforce cost planning and integrates workforce results into broader financial planning. Delivery Types: On-Demand (OD) Duration: 2 hours Availability: A La Carte, Navigator Passport Course Link: Configuring Workforce Planning Expressagoralewski1 day agoCommunity Manager43Views0likes0CommentsRead only option for cube views
Hi , We have DEV and PROD environment and we want users to have read-only option to cube views in PROD environment. If we restrict the cube view group maintenance, users could execute the cube view but not see how it defined. All options are greyed out like shown in below picture. Is it possible to have Read-only option for cube view in production?suresh_klarna1 day agoNew Contributor III19Views0likes1CommentProblem With DM Sequence Parameter Resolution Order
Good afternoon, I'm encountering an issue and hoping someone could share how they've worked around this in the past. We have a task that runs over a finite number of Entities. Historically the underlying Business Rule was kicked off via DM Step with Entity: E#Top.Base. It's not exactly "Top" but something similar to hopefully capture all relevant Entities to this particular task. The runtime was always a consideration as it also runs for all remaining periods within our current year. In an effort to reduce runtime we introduced a "Unique Entity" parameter. This is defined by a Business Rule that runs and queries a source table to determine only the Entities that this particular task needs to be run on. In practice this has greatly reduced runtime on the task. Runs more than 2x faster. The problem: The source table that populates the list of "unique entities" is also updated within the same Data Management Sequence. Something like: 1) Update source table 2) Run task described above Because both Steps are within the same Sequence, the "Unique Entity" business rule is being executed before #1 Update source table happens. So any changes to the source table versus a previous run could cause an Entity to be missed. I wish the Filter would get resolved when the Step is executed instead of when the Sequence is executed. Hopefully that makes sense to everyone? The only workaround that I'm aware of is to kick off the 2nd Step at the end of the 1st step. This removes a lot of the visibility and, as part of our current process, Step 1 is sometimes executed on its own to preview Stage data before the full Cube posting takes place. I appreciate anyone who can provide guidance on this matter. Thanks, BrandonSolvedbmcwilliams2 days agoNew Contributor40Views0likes5Comments
Getting Started
Learn more about the OneStream Community with the links below.