namespace AlmToolkit { using BismNormalizer.TabularCompare.Core; using BismNormalizer.TabularCompare; using Model; using Newtonsoft.Json; using System.Collections.Generic; using System; public class ComparisonJSInteraction { #region Private members private Comparison _comparison; // The form class needs to be changed according to yours private static ComparisonForm _instanceMainForm = null; // Used to maintain a dictionary with direct access to the Angular node and C# comparison object private Dictionary _directAccessList = new Dictionary(); #endregion #region Public properties // List to be used to populate data in grid control. This needs to be static, since everytime CEF Sharp invokes the method, it creates a new instance // Need to revisit initialization to evaluate removal strategy public static List comparisonList = new List(); public static List selectedNodes = new List(); public ComparisonJSInteraction(ComparisonForm mainForm) { _instanceMainForm = mainForm; } public Comparison Comparison { get { return _comparison; } set { _comparison = value; } } #endregion #region Angular endpoints /// /// Method that sends flattened comparison object to Angular control /// /// public string GetComparisonList() { string comparisonData = JsonConvert.SerializeObject(comparisonList); return comparisonData; } /// /// Save or Compare as per the action on UI /// /// Action to be performed public void SaveOrCompare(string action) { switch (action.ToLower()) { case "save": _instanceMainForm.SaveNg(); break; case "compare": _instanceMainForm.InitializeAndCompareTabularModelsNg(); break; } } /// /// Update the object as and when selected action is changed on UI /// /// Id of the node updated /// New selected action /// Old selected action public void ChangeOccurred(int id, string newAction, string oldAction) { if (_directAccessList.ContainsKey(id)) { AngularComposite currentNode = _directAccessList[id]; // if set parent to skip/create/delete, MAY need to set all children to skip/create/delete too (only the read only cells) switch (newAction) { case "Skip": currentNode.dotNetComparison.MergeAction = MergeAction.Skip; currentNode.ngComparison.MergeAction = MergeAction.Skip.ToString(); CheckToSkipChildren(currentNode.ngComparison); break; case "Create": currentNode.dotNetComparison.MergeAction = MergeAction.Create; currentNode.ngComparison.MergeAction = MergeAction.Create.ToString(); CheckToCreateChildren(currentNode.ngComparison); break; case "Delete": currentNode.dotNetComparison.MergeAction = MergeAction.Delete; currentNode.ngComparison.MergeAction = MergeAction.Delete.ToString(); CheckToDeleteChildren(currentNode.ngComparison); break; case "Update": currentNode.dotNetComparison.MergeAction = MergeAction.Update; currentNode.ngComparison.MergeAction = MergeAction.Update.ToString(); break; default: break; } // Disable update menu on comparison change _instanceMainForm.HandleComparisonChanged(); // Refresh the tree control, since grid is maintained here _instanceMainForm.refreshGridControl(true); } } /// /// Perform required action on selected nodes /// /// Action to be performed: Skip, Update, Create or Delete /// List of Node Ids which are selected on Angular control public void PerformActionsOnSelectedActions(string action, List selectedNodesUI) { selectedNodes.Clear(); ComparisonNode nodeToAdd; for (int nodeCounter = 0; nodeCounter < selectedNodesUI.Count; nodeCounter++) { if (_directAccessList.ContainsKey(Convert.ToInt32(selectedNodesUI[nodeCounter]))) { AngularComposite currentNode = _directAccessList[Convert.ToInt32(selectedNodesUI[nodeCounter])]; nodeToAdd = currentNode.ngComparison; selectedNodes.Add(nodeToAdd); } } switch (action) { case "skip": SkipItems(true); break; case "create": CreateItems(true); break; case "delete": DeleteItems(true); break; case "update": UpdateItems(true); break; } // Disable update menu on comparison change _instanceMainForm.HandleComparisonChanged(); // Refresh the tree control, since grid is maintained here _instanceMainForm.refreshGridControl(true); } #endregion #region Data transformation and population /// /// Transform comparison object to structure understood by Angular control /// public void SetComparisonData() { if (this._comparison != null) { comparisonList.Clear(); _directAccessList.Clear(); foreach (ComparisonObject comparisonObject in this._comparison.ComparisonObjects) { this.PopulateComparisonData(comparisonObject, 0, null); } } } /// /// Helper method to transform comparison object to structure understood by Angular control /// /// Individual node in the tree /// Level in the heirarchy to which the object belongs /// Reference to the parent node of the current object private void PopulateComparisonData(ComparisonObject comparisonObject, int level, ComparisonNode parentNode) { if (comparisonObject != null) { string nodeType = ""; switch (comparisonObject.ComparisonObjectType) { case ComparisonObjectType.DataSource: nodeType = "Data Source"; break; case ComparisonObjectType.CalculationItem: nodeType = "Calculation Item"; break; case ComparisonObjectType.Table: //Check if source table has any calc item children. If yes, it's a calc group. bool isCalcGroup = false; foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects) { if (childComparisonObject.ComparisonObjectType == ComparisonObjectType.CalculationItem && childComparisonObject.Status != ComparisonObjectStatus.MissingInSource) { isCalcGroup = true; break; } } nodeType = isCalcGroup ? "Calculation Group" : "Table"; break; default: nodeType = comparisonObject.ComparisonObjectType.ToString(); break; } ComparisonNode currentNode = new ComparisonNode { NodeType = nodeType, SourceName = comparisonObject.SourceObjectName, TargetName = comparisonObject.TargetObjectName, SourceInternalName = comparisonObject.SourceObjectInternalName, TargetInternalName = comparisonObject.TargetObjectInternalName, SourceObjectDefinition = comparisonObject.SourceObjectDefinition, TargetObjectDefinition = comparisonObject.TargetObjectDefinition, ShowNode = true, Level = level, MergeAction = comparisonObject.MergeAction.ToString(), DisableMessage = "", DropdownDisabled = false }; if (parentNode != null) { currentNode.ParentId = parentNode.Id; parentNode.ChildNodes.Add(currentNode.Id); } switch (comparisonObject.Status) { case ComparisonObjectStatus.MissingInTarget: currentNode.Status = "Missing in Target"; break; case ComparisonObjectStatus.MissingInSource: currentNode.Status = "Missing in Source"; break; case ComparisonObjectStatus.SameDefinition: currentNode.Status = "Same Definition"; break; case ComparisonObjectStatus.DifferentDefinitions: currentNode.Status = "Different Definitions"; break; default: break; } comparisonList.Add(currentNode); // Populate helper objects AngularComposite angularComposite = new AngularComposite(currentNode, comparisonObject); _directAccessList.Add(currentNode.Id, angularComposite); // set drop-down to have limited members based on what is available switch (comparisonObject.MergeAction) { case MergeAction.Create: currentNode.AvailableActions = new List { "Create", "Skip" }; if (parentNode != null && string.Equals(parentNode.Status, "Missing in Target") && string.Equals(parentNode.MergeAction, "Skip")) { comparisonObject.MergeAction = MergeAction.Skip; currentNode.MergeAction = MergeAction.Skip.ToString(); currentNode.DropdownDisabled = true; SetNodeTooltip(angularComposite, true); } break; case MergeAction.Update: currentNode.AvailableActions = new List { "Update", "Skip" }; break; case MergeAction.Delete: currentNode.AvailableActions = new List { "Delete", "Skip" }; //check if parent is also set to delete, in which case make this cell readonly if (parentNode != null && string.Equals(parentNode.MergeAction, "Delete")) { currentNode.DropdownDisabled = true; SetNodeTooltip(angularComposite, true); } break; case MergeAction.Skip: switch (comparisonObject.Status) { case ComparisonObjectStatus.MissingInTarget: currentNode.AvailableActions = new List { "Create", "Skip" }; //check if parent is also MissingInTarget and Skip, make this cell readonly if (parentNode != null && string.Equals(parentNode.Status, "Missing in Target") && string.Equals(parentNode.MergeAction, "Skip")) { currentNode.DropdownDisabled = true; SetNodeTooltip(angularComposite, true); } break; case ComparisonObjectStatus.MissingInSource: currentNode.AvailableActions = new List { "Delete", "Skip" }; break; case ComparisonObjectStatus.DifferentDefinitions: currentNode.AvailableActions = new List { "Update", "Skip" }; break; default: //default covers ComparisonObjectStatus.SameDefinition: which is most common case (above cases are for saved skip selections from file) currentNode.AvailableActions = new List { "Skip" }; currentNode.DropdownDisabled = true; SetNodeTooltip(angularComposite, true); break; } break; default: break; }; // Add child objects if it exists if (comparisonObject.ChildComparisonObjects != null && comparisonObject.ChildComparisonObjects.Count > 0) { foreach (ComparisonObject childComparisonObject in comparisonObject.ChildComparisonObjects) { PopulateComparisonData(childComparisonObject, level + 1, currentNode); } } } } #endregion #region Helper functions /// /// Set visibility of Angular node /// /// Show or hide node /// Display name of the node for source /// Internal name of the node for source /// Display name of the node for target /// Internal name of the node for target /// Object type i.e. Data source, KPI, Measure private void SetNodeVisibility(bool IsVisible, AngularComposite node) { if (node != null) { //node.IsVisible = IsVisible; node.ngComparison.ShowNode = IsVisible; } } private void SetNodeTooltip(AngularComposite node, bool disabledDueToParent) { node.ngComparison.DisableMessage = (disabledDueToParent ? "This object's action option is disabled due to a parent object selection" : ""); } #endregion #region Menu actions /// /// Show or Hide skip nodes /// /// Hide Skip nodes /// Hide objects only in case of same definition public void ShowHideSkipNodes(bool hide, bool sameDefinitionFilter = false) { if (this._comparison != null) { foreach (ComparisonNode node in comparisonList) { ShowHideSkipNodes(node, hide, sameDefinitionFilter); } } } /// /// Show or hide skip nodes /// /// List of comparison objects for whom children are to be checked /// Show or hide the node /// Hide nodes with same definition private void ShowHideSkipNodes(ComparisonNode node, bool hide, bool sameDefinitionFilter) { bool isVisible = true; if (node.MergeAction.ToString() == "Skip" && (!sameDefinitionFilter || (sameDefinitionFilter && hide && node.Status.ToString() == "Same Definition"))) { // if currently selected skip item contains Update, Delete or Create children, then need to keep visible - or result in orphans bool foundCreateOrDeleteChild = false; foreach (int childNodeId in node.ChildNodes) { if (_directAccessList.ContainsKey(childNodeId)) { AngularComposite childNode = _directAccessList[childNodeId]; if (childNode.dotNetComparison.MergeAction == MergeAction.Update || childNode.dotNetComparison.MergeAction == MergeAction.Delete || childNode.dotNetComparison.MergeAction == MergeAction.Create) { foundCreateOrDeleteChild = true; break; } } } if (hide) { if (!foundCreateOrDeleteChild) { isVisible = false; } } else { isVisible = true; } } else { isVisible = ( !(node.MergeAction.ToString() == "Skip " && (node.ChildNodes.Count == 0 || !NodeContainsEditableChildren(node, hide)))); } if (_directAccessList.ContainsKey(node.Id)) { AngularComposite childNode = _directAccessList[node.Id]; SetNodeVisibility(isVisible, childNode); } foreach (int childNodeId in node.ChildNodes) { if (_directAccessList.ContainsKey(childNodeId)) { AngularComposite childNode = _directAccessList[childNodeId]; ShowHideSkipNodes(childNode.ngComparison, hide, sameDefinitionFilter); } } } /// /// Check if node contains editable children /// /// Node for which children is to be checked /// Hide or show /// private bool NodeContainsEditableChildren(ComparisonNode node, bool hide) { bool containsChildren = false; foreach (int childNodeId in node.ChildNodes) { if (_directAccessList.ContainsKey(childNodeId)) { AngularComposite childComposite = _directAccessList[childNodeId]; ComparisonNode childNode = childComposite.ngComparison; if ((hide && childNode.MergeAction != "Skip " && childNode.MergeAction != "Skip") || (!hide && childNode.MergeAction != "Skip ")) { containsChildren = true; } else { bool childContainsChildren = NodeContainsEditableChildren(childNode, hide); if (!containsChildren) { containsChildren = childContainsChildren; } } if (childNode.MergeAction.ToString() == "Skip") { SetNodeVisibility(!hide, childComposite); } } } if (node.MergeAction.ToString() != "Skip") { if (_directAccessList.ContainsKey(node.Id)) { AngularComposite nodeComposite = _directAccessList[node.Id]; SetNodeVisibility(containsChildren, nodeComposite); } } return containsChildren; } /********** Set node to skip depending on comparison object status ****************/ /// /// Sets Action property of objects to Skip within given range. /// /// /// public void SkipItems(bool selectedOnly, ComparisonObjectStatus comparisonObjectStatus = ComparisonObjectStatus.Na) //Na because won't take null cos it's an enum { List listToUse = (selectedOnly ? selectedNodes : comparisonList); foreach (ComparisonNode node in listToUse) { // In case of selected only, check if item is present in selected objects SkipItemPrivate(comparisonObjectStatus, node); } } private void SkipItemPrivate(ComparisonObjectStatus comparisonObjectStatus, ComparisonNode row) { if (comparisonObjectStatus == ComparisonObjectStatus.Na || (comparisonObjectStatus == ComparisonObjectStatus.DifferentDefinitions && row.Status == "Different Definitions") || (comparisonObjectStatus == ComparisonObjectStatus.MissingInSource && row.Status == "Missing in Source") || (comparisonObjectStatus == ComparisonObjectStatus.MissingInTarget && row.Status == "Missing in Target")) { bool isReadOnly = row.DropdownDisabled; if (!isReadOnly && row.MergeAction != MergeAction.Skip.ToString() //&& //row.Cells[8].Value.ToString() != "Set Parent Node" -- Need to check where is this value set ) { row.MergeAction = MergeAction.Skip.ToString(); if (_directAccessList.ContainsKey(row.Id)) { AngularComposite node = _directAccessList[row.Id]; node.dotNetComparison.MergeAction = MergeAction.Skip; CheckToSkipChildren(row); } } } } private void CheckToSkipChildren(ComparisonNode selectedRow) { // if Missing in Target (default is create) and user selects skip, definitely can't create child objects, so set them to skip too and disable them if (selectedRow.Status == "Missing in Target") { //TreeGridNode selectedNode = FindNodeByIDs(selectedRow.Cells[0].Value.ToString(), selectedRow.Cells[2].Value.ToString(), selectedRow.Cells[6].Value.ToString()); foreach (int node in selectedRow.ChildNodes) { SetNodeToSkip(node); } } // if Missing in Source (default is delete) and user selects skip, he may still want to delete some child objects, so ensure they are enabled else if (selectedRow.Status == "Missing in Source") { //TreeGridNode selectedNode = FindNodeByIDs(selectedRow.Cells[0].Value.ToString(), selectedRow.Cells[2].Value.ToString(), selectedRow.Cells[6].Value.ToString()); foreach (int nodeId in selectedRow.ChildNodes) { if (_directAccessList.ContainsKey(nodeId)) { AngularComposite node = _directAccessList[nodeId]; if (node.ngComparison.AvailableActions.Contains("Skip")) { node.ngComparison.DropdownDisabled = false; SetNodeTooltip(node, false); } } } } } private void SetNodeToSkip(int nodeId) { if (_directAccessList.ContainsKey(nodeId)) { AngularComposite node = _directAccessList[nodeId]; if (node.ngComparison.AvailableActions.Contains("Skip")) { node.ngComparison.MergeAction = MergeAction.Skip.ToString(); node.ngComparison.DropdownDisabled = true; node.dotNetComparison.MergeAction = MergeAction.Skip; SetNodeTooltip(node, true); } foreach (int childNode in node.ngComparison.ChildNodes) { SetNodeToSkip(childNode); } } } /************* End section ****************/ /********** Set node to update ****************/ /// /// Set actions for node with different definitions to update /// /// Set for selected nodes or all nodes public void UpdateItems(bool selectedOnly) { // If selected only, pick items from selected list List listToUse = (selectedOnly ? selectedNodes : comparisonList); // Not necessary to run twice with internal method because Updates don't impact children foreach (ComparisonNode item in listToUse) { if (item.AvailableActions.Contains("Update")) { item.MergeAction = MergeAction.Update.ToString(); // Set merge action in corresponding comparison list _directAccessList[item.Id].dotNetComparison.MergeAction = MergeAction.Update; } } } /************* End section ****************/ /********** Set node to create ****************/ /// /// Sets Action property of objects to Create within given range. /// /// public void CreateItems(bool selectedOnly) { List listToUse = (selectedOnly ? selectedNodes : comparisonList); foreach (ComparisonNode item in listToUse) { //DataGridViewRow row = (selectedOnly ? this.SelectedRows[i] : this.Rows[i]); bool isReadOnly = item.DropdownDisabled; if (!isReadOnly && item.MergeAction != "Skip " // This condition is not working in existing code. Retained for consistency with existing code. && item.AvailableActions.Contains(MergeAction.Create.ToString())) { item.MergeAction = MergeAction.Create.ToString(); // Set merge action in corresponding comparison list _directAccessList[item.Id].dotNetComparison.MergeAction = MergeAction.Create; // Check status of children CheckToCreateChildren(item); } } } private void CheckToCreateChildren(ComparisonNode selectedRow) { // if Missing in Target (default is create) and user selects create, he may still want to skip some child objects, so ensure they are enabled if (selectedRow.Status.ToString() == "Missing in Target") { foreach (int nodeId in selectedRow.ChildNodes) { AngularComposite node = _directAccessList[nodeId]; if (node.ngComparison.AvailableActions.Contains(MergeAction.Create.ToString())) { node.ngComparison.DropdownDisabled = false; SetNodeTooltip(node, false); } } } } /************* End section ****************/ /********** Set node to delete ****************/ /// /// Sets Action property of objects to Delete within given range. /// /// public void DeleteItems(bool selectedOnly) { List listToUse = (selectedOnly ? selectedNodes : comparisonList); foreach (ComparisonNode item in listToUse) { bool isReadOnly = item.DropdownDisabled; if (!isReadOnly && item.MergeAction != "Skip " // This condition is not working in existing code. Retained for consistency with existing code. && item.AvailableActions.Contains(MergeAction.Delete.ToString())) { item.MergeAction = MergeAction.Delete.ToString(); // Set merge action in corresponding comparison list _directAccessList[item.Id].dotNetComparison.MergeAction = MergeAction.Delete; // Check status of children CheckToDeleteChildren(item); } } } private void CheckToDeleteChildren(ComparisonNode selectedRow) { // if Missing in Source (default is delete) and user selects delete, definitely can't skip child objects, so set them to delete too and disable them if (selectedRow.Status == "Missing in Source") { foreach (int node in selectedRow.ChildNodes) { SetNodeToDelete(node); } } } private void SetNodeToDelete(int nodeId) { if (_directAccessList.ContainsKey(nodeId)) { AngularComposite node = _directAccessList[nodeId]; if (node.ngComparison.AvailableActions.Contains("Delete")) { node.ngComparison.MergeAction = MergeAction.Delete.ToString(); node.ngComparison.DropdownDisabled = true; node.dotNetComparison.MergeAction = MergeAction.Delete; SetNodeTooltip(node, true); } foreach (int childNode in node.ngComparison.ChildNodes) { SetNodeToDelete(childNode); } } } /************* End section ****************/ #endregion } }