<commit_before>'use strict';

angular.module('deckApp.pipelines.graph.directive', [
  'deckApp.utils.d3',
  'deckApp.utils.lodash',
  'deckApp.utils.jQuery',
])
  .directive('pipelineGraph', function ($window, d3Service, _, $) {
    return {
      restrict: 'E',
      scope: {
        pipeline: '=',
        viewState: '=',
        onNodeClick: '=',
      },
      templateUrl: 'scripts/modules/pipelines/config/graph/pipelineGraph.directive.html',
      link: function (scope, elem) {

        scope.nodeRadius = 8;
        scope.rowPadding = 30;
        scope.graphVerticalPadding = 15;
        scope.labelOffsetX = scope.nodeRadius + 3;
        scope.labelOffsetY = scope.nodeRadius + 10;

        /**
         * Used to draw inverse bezier curve between stages
         */
        var diagonal = d3Service.svg.diagonal()
          .source(function(d) { return {'x': d.source.y, 'y': d.source.x + scope.nodeRadius}; })
          .target(function(d) { return {'x': d.target.y, 'y': d.target.x - scope.nodeRadius}; })
          .projection(function (d) {
            return [d.y, d.x];
          });


        scope.nodeClicked = function(node) {
          scope.onNodeClick(node.section, node.index);
        };

        scope.highlight = function(node) {
          if (node.isActive) {
            return;
          }
          node.isHighlighted = true;
          node.parentLinks.forEach(function(link) {
            link.isHighlighted = true;
            link.className = 'highlighted target';
          });
          node.childLinks.forEach(function(link) {
            link.isHighlighted = true;
            link.className = 'highlighted source';
          });
        };

        scope.removeHighlight = function(node) {
          if (node.isActive) {
            return;
          }
          node.isHighlighted = false;
          node.parentLinks.forEach(function(link) {
            link.isHighlighted = false;
            link.className = link.parent.isActive ? 'active source' : 'inactive';
          });
          node.childLinks.forEach(function(link) {
            link.isHighlighted = false;
            link.className = link.child.isActive ? 'active target' : 'inactive';
          });
        };

        /**
         * Creates base configuration for all nodes;
         *  - does not set phase, position, or create links between nodes
         */
        function createNodes() {
          var triggersNode = {
              name: 'Triggers (' + scope.pipeline.triggers.length + ')',
              phase: 0,
              id: -1,
              section: 'triggers',
              parentIds: [],
              parents: [],
              children: [],
              parentLinks: [],
              childLinks: [],
              isActive: scope.viewState.section === 'triggers',
              isHighlighted: false,
            },
            nodes = [triggersNode];

          scope.pipeline.parallel = true;
          scope.pipeline.stages.forEach(function(stage, idx) {
            stage.requisiteStageRefIds = stage.requisiteStageRefIds || [];
            var node = {
              id: stage.refId,
              name: stage.name || '[new stage]',
              section: 'stage',
              index: idx,
              parentIds: angular.copy(stage.requisiteStageRefIds || []),
              parents: [],
              children: [],
              parentLinks: [],
              childLinks: [],
              isActive: scope.viewState.stageIndex === idx && scope.viewState.section === 'stage',
              isHighlighted: false,
            };
            if (!node.parentIds.length) {
              node.parentIds.push(triggersNode.id);
            }
            nodes.push(node);
          });
          return nodes;
        }

        /**
         * Sets phases and adds children/parents to nodes
         * Probably blows the stack if circular dependencies exist, maybe not
         */
        function applyPhasesAndLink(nodes) {
          nodes = nodes || createNodes();
          var allPhasesResolved = true;
          nodes.forEach(function(node) {
            var phaseResolvable = true,
                phase = 0;
            if (!node.parentIds.length) {
              node.phase = phase;
            } else {
              node.parentIds.forEach(function(parentId) {
                var parent = _.find(nodes, { id: parentId });
                if (parent.phase === undefined) {
                  phaseResolvable = false;
                } else {
                  phase = Math.max(phase, parent.phase);
                  parent.children.push(node);
                  node.parents.push({node: parent});
                }
              });
              if (phaseResolvable) {
                node.phase = phase + 1;
              } else {
                allPhasesResolved = false;
              }
            }
          });
          if (!allPhasesResolved) {
            applyPhasesAndLink(nodes);
          } else {
            scope.phaseCount = _.max(nodes, 'phase').phase;
            if (scope.phaseCount > 6) {
              scope.nodeRadius = 6;
              scope.labelOffsetX = scope.nodeRadius + 3;
              scope.labelOffsetY = 15;
            }
            scope.nodes = [];
            nodes.forEach(function(node) {
              node.children = _.uniq(node.children);
              node.parents = _.uniq(node.parents);
            });

            // Collision minimization "Algorithm"
            nodes = _.sortByAll(nodes,
              'phase',
              function(node) {
                return 1 - _.sum(node.children, function(child) { return child.children.length; });
              },
              function(node) {
                return _.sortBy(node.parents, 'phase').map(function(parent) {
                  return [(node.phase - parent.phase), parent.name].join('-');
                });
              },
              function(node) {
                return _.sortBy(node.children, 'phase').map(function(child) {
                  return [(child.phase - node.phase), child.name].join('-');
                });
              },
              'name'
            );
            nodes.forEach(function(node) {
              scope.nodes[node.phase] = scope.nodes[node.phase] || [];
              scope.nodes[node.phase].push(node);
            });
          }<commit_msg>sort by refId before name<commit_after>'use strict';

angular.module('deckApp.pipelines.graph.directive', [
  'deckApp.utils.d3',
  'deckApp.utils.lodash',
  'deckApp.utils.jQuery',
])
  .directive('pipelineGraph', function ($window, d3Service, _, $) {
    return {
      restrict: 'E',
      scope: {
        pipeline: '=',
        viewState: '=',
        onNodeClick: '=',
      },
      templateUrl: 'scripts/modules/pipelines/config/graph/pipelineGraph.directive.html',
      link: function (scope, elem) {

        scope.nodeRadius = 8;
        scope.rowPadding = 30;
        scope.graphVerticalPadding = 15;
        scope.labelOffsetX = scope.nodeRadius + 3;
        scope.labelOffsetY = scope.nodeRadius + 10;

        /**
         * Used to draw inverse bezier curve between stages
         */
        var diagonal = d3Service.svg.diagonal()
          .source(function(d) { return {'x': d.source.y, 'y': d.source.x + scope.nodeRadius}; })
          .target(function(d) { return {'x': d.target.y, 'y': d.target.x - scope.nodeRadius}; })
          .projection(function (d) {
            return [d.y, d.x];
          });


        scope.nodeClicked = function(node) {
          scope.onNodeClick(node.section, node.index);
        };

        scope.highlight = function(node) {
          if (node.isActive) {
            return;
          }
          node.isHighlighted = true;
          node.parentLinks.forEach(function(link) {
            link.isHighlighted = true;
            link.className = 'highlighted target';
          });
          node.childLinks.forEach(function(link) {
            link.isHighlighted = true;
            link.className = 'highlighted source';
          });
        };

        scope.removeHighlight = function(node) {
          if (node.isActive) {
            return;
          }
          node.isHighlighted = false;
          node.parentLinks.forEach(function(link) {
            link.isHighlighted = false;
            link.className = link.parent.isActive ? 'active source' : 'inactive';
          });
          node.childLinks.forEach(function(link) {
            link.isHighlighted = false;
            link.className = link.child.isActive ? 'active target' : 'inactive';
          });
        };

        /**
         * Creates base configuration for all nodes;
         *  - does not set phase, position, or create links between nodes
         */
        function createNodes() {
          var triggersNode = {
              name: 'Triggers (' + scope.pipeline.triggers.length + ')',
              phase: 0,
              id: -1,
              section: 'triggers',
              parentIds: [],
              parents: [],
              children: [],
              parentLinks: [],
              childLinks: [],
              isActive: scope.viewState.section === 'triggers',
              isHighlighted: false,
            },
            nodes = [triggersNode];

          scope.pipeline.parallel = true;
          scope.pipeline.stages.forEach(function(stage, idx) {
            stage.requisiteStageRefIds = stage.requisiteStageRefIds || [];
            var node = {
              id: stage.refId,
              name: stage.name || '[new stage]',
              section: 'stage',
              index: idx,
              parentIds: angular.copy(stage.requisiteStageRefIds || []),
              parents: [],
              children: [],
              parentLinks: [],
              childLinks: [],
              isActive: scope.viewState.stageIndex === idx && scope.viewState.section === 'stage',
              isHighlighted: false,
            };
            if (!node.parentIds.length) {
              node.parentIds.push(triggersNode.id);
            }
            nodes.push(node);
          });
          return nodes;
        }

        /**
         * Sets phases and adds children/parents to nodes
         * Probably blows the stack if circular dependencies exist, maybe not
         */
        function applyPhasesAndLink(nodes) {
          nodes = nodes || createNodes();
          var allPhasesResolved = true;
          nodes.forEach(function(node) {
            var phaseResolvable = true,
                phase = 0;
            if (!node.parentIds.length) {
              node.phase = phase;
            } else {
              node.parentIds.forEach(function(parentId) {
                var parent = _.find(nodes, { id: parentId });
                if (parent.phase === undefined) {
                  phaseResolvable = false;
                } else {
                  phase = Math.max(phase, parent.phase);
                  parent.children.push(node);
                  node.parents.push({node: parent});
                }
              });
              if (phaseResolvable) {
                node.phase = phase + 1;
              } else {
                allPhasesResolved = false;
              }
            }
          });
          if (!allPhasesResolved) {
            applyPhasesAndLink(nodes);
          } else {
            scope.phaseCount = _.max(nodes, 'phase').phase;
            if (scope.phaseCount > 6) {
              scope.nodeRadius = 6;
              scope.labelOffsetX = scope.nodeRadius + 3;
              scope.labelOffsetY = 15;
            }
            scope.nodes = [];
            nodes.forEach(function(node) {
              node.children = _.uniq(node.children);
              node.parents = _.uniq(node.parents);
            });

            // Collision minimization "Algorithm"
            nodes = _.sortByAll(nodes,
              'phase',
              function(node) {
                return 1 - _.sum(node.children, function(child) { return child.children.length; });
              },
              function(node) {
                return _.sortBy(node.parents, 'phase').map(function(parent) {
                  return [(node.phase - parent.phase), parent.name].join('-');
                });
              },
              function(node) {
                return _.sortBy(node.children, 'phase').map(function(child) {
                  return [(child.phase - node.phase), child.name].join('-');
                });
              },
              'refId',
              'name'
            );
            nodes.forEach(function(node) {
              scope.nodes[node.phase] = scope.nodes[node.phase] || [];
              scope.nodes[node.phase].push(node);
            });
          }
{'fraction_non_alphanumeric': 0.12443990252338652, 'fraction_numerical': 0.003301627230563635, 'mean_word_length': 1.4325047801147228, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1793855', 'n_tokens_mistral': 3410, 'n_tokens_neox': 3292, 'n_words': 621}