<commit_before>import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { set, get, computed } from '@ember/object';
import { next } from '@ember/runloop';

import {
  createRoute,
  getSplitters,
  getRoutes,
  getResolvers,
} from 'consul-ui/utils/components/discovery-chain/index';

export default Component.extend({
  dom: service('dom'),
  ticker: service('ticker'),
  dataStructs: service('data-structs'),
  classNames: ['discovery-chain'],
  classNameBindings: ['active'],
  isDisplayed: false,
  selectedId: '',
  x: 0,
  y: 0,
  tooltip: '',
  activeTooltip: false,
  init: function() {
    this._super(...arguments);
    this._listeners = this.dom.listeners();
    this._viewportlistener = this.dom.listeners();
  },
  didInsertElement: function() {
    this._super(...arguments);
    this._viewportlistener.add(
      this.dom.isInViewport(this.element, bool => {
        if (get(this, 'isDisplayed') !== bool) {
          set(this, 'isDisplayed', bool);
          if (this.isDisplayed) {
            this.addPathListeners();
          } else {
            this.ticker.destroy(this);
          }
        }
      })
    );
  },
  didReceiveAttrs: function() {
    this._super(...arguments);
    if (this.element) {
      this.addPathListeners();
    }
  },
  willDestroyElement: function() {
    this._super(...arguments);
    this._listeners.remove();
    this._viewportlistener.remove();
    this.ticker.destroy(this);
  },
  splitters: computed('chain.Nodes', function() {
    return getSplitters(get(this, 'chain.Nodes'));
  }),
  routes: computed('chain.Nodes', function() {
    const routes = getRoutes(get(this, 'chain.Nodes'), this.dom.guid);
    // if we have no routes with a PathPrefix of '/' or one with no definition at all
    // then add our own 'default catch all'
    if (
      !routes.find(item => get(item, 'Definition.Match.HTTP.PathPrefix') === '/') &&
      !routes.find(item => typeof item.Definition === 'undefined')
    ) {
      let nextNode;
      const resolverID = `resolver:${this.chain.ServiceName}.${this.chain.Namespace}.${this.chain.Datacenter}`;
      const splitterID = `splitter:${this.chain.ServiceName}.${this.chain.Namespace}`;
      if (typeof this.chain.Nodes[resolverID] !== 'undefined') {
        nextNode = resolverID;
      } else if (typeof this.chain.Nodes[splitterID] !== 'undefined') {
        nextNode = splitterID;
      }
      if (typeof nextNode !== 'undefined') {
        routes.push({
          Default: true,
          ID: `route:${this.chain.ServiceName}`,
          Name: this.chain.ServiceName,
          Definition: {
            Match: {
              HTTP: {
                PathPrefix: '/',
              },
            },
          },
          NextNode: nextNode,
        });
      }
    }
    return routes;
  }),
  resolvers: computed('chain.{Nodes,Targets}', function() {
    return getResolvers(
      this.chain.Datacenter,
      this.chain.Namespace,
      get(this, 'chain.Targets'),
      get(this, 'chain.Nodes')
    );
  }),
  graph: computed('splitters', 'routes', function() {
    const graph = this.dataStructs.graph();
    const router = this.chain.ServiceName;
    this.splitters.forEach(item => {
      item.Splits.forEach(splitter => {
        graph.addLink(item.ID, splitter.NextNode);
      });
    });
    this.routes.forEach((route, i) => {
      route = createRoute(route, router, this.dom.guid);
      graph.addLink(route.ID, route.NextNode);
    });
    return graph;
  }),
  selected: computed('selectedId', 'graph', function() {
    if (this.selectedId === '' || !this.dom.element(`#${this.selectedId}`)) {
      return {};
    }
    const id = this.selectedId;
    const type = id.split(':').shift();
    const nodes = [id];
    const edges = [];
    this.graph.forEachLinkedNode(id, (linkedNode, link) => {
      nodes.push(linkedNode.id);
      edges.push(`${link.fromId}>${link.toId}`);
      this.graph.forEachLinkedNode(linkedNode.id, (linkedNode, link) => {
        const nodeType = linkedNode.id.split(':').shift();
        if (type !== nodeType && type !== 'splitter' && nodeType !== 'splitter') {
          nodes.push(linkedNode.id);
          edges.push(`${link.fromId}>${link.toId}`);
        }
      });
    });
    return {
      nodes: nodes.map(item => `#${CSS.escape(item)}`),
      edges: edges.map(item => `#${CSS.escape(item)}`),
    };
  }),
  width: computed('isDisplayed', 'chain.{Nodes,Targets}', function() {
    return this.element.offsetWidth;
  }),
  height: computed('isDisplayed', 'chain.{Nodes,Targets}', function() {
    return this.element.offsetHeight;
  }),
  // TODO(octane): ember has trouble adding mouse events to svg elements whilst giving
  // the developer access to the mouse event therefore we just use JS to add our events
  // revisit this post Octane
  addPathListeners: function() {
    // TODO: Figure out if we can remove this next
    next(() => {
      this._listeners.remove();
      this._listeners.add(this.dom.document(), {
        click: e => {
          // all route/splitter/resolver components currently
          // have classes that end in '-card'
          if (!this.dom.closest('[class$="-card"]', e.target)) {
            set(this, 'active', false);
            set(this, 'selectedId', '');
          }
        },
      });
      [...this.dom.elements('path.split', this.element)].forEach(item => {
        this._listeners.add(item, {
          mouseover: e => this.actions.showSplit.apply(this, [e]),
          mouseout: e => this.actions.hideSplit.apply(this, [e]),
        });
      });
    });
    // TODO: currently don't think there is a way to listen
    // for an element being removed inside a component, possibly
    // using IntersectionObserver. It's a tiny detail, but we just always
    // remove the tooltip on component update as its so tiny, ideal
    // the tooltip would stay if there was no change to the <path>
    // set(this, 'activeTooltip', false);
  },
  actions: {
    showSplit: function(e) {
      this.setProperties({
        x: e.offsetX,
        y: e.offsetY - 5,
        tooltip: e.target.dataset.percentage,
        activeTooltip: true,
      });<commit_msg>ui: Remove `next` from usage from discovery-chain component (#7273)<commit_after>import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { set, get, computed } from '@ember/object';

import {
  createRoute,
  getSplitters,
  getRoutes,
  getResolvers,
} from 'consul-ui/utils/components/discovery-chain/index';

export default Component.extend({
  dom: service('dom'),
  ticker: service('ticker'),
  dataStructs: service('data-structs'),
  classNames: ['discovery-chain'],
  classNameBindings: ['active'],
  isDisplayed: false,
  selectedId: '',
  x: 0,
  y: 0,
  tooltip: '',
  activeTooltip: false,
  init: function() {
    this._super(...arguments);
    this._listeners = this.dom.listeners();
    this._viewportlistener = this.dom.listeners();
  },
  didInsertElement: function() {
    this._super(...arguments);
    this._viewportlistener.add(
      this.dom.isInViewport(this.element, bool => {
        if (get(this, 'isDisplayed') !== bool) {
          set(this, 'isDisplayed', bool);
          if (this.isDisplayed) {
            this.addPathListeners();
          } else {
            this.ticker.destroy(this);
          }
        }
      })
    );
  },
  didReceiveAttrs: function() {
    this._super(...arguments);
    if (this.element) {
      this.addPathListeners();
    }
  },
  willDestroyElement: function() {
    this._super(...arguments);
    this._listeners.remove();
    this._viewportlistener.remove();
    this.ticker.destroy(this);
  },
  splitters: computed('chain.Nodes', function() {
    return getSplitters(get(this, 'chain.Nodes'));
  }),
  routes: computed('chain.Nodes', function() {
    const routes = getRoutes(get(this, 'chain.Nodes'), this.dom.guid);
    // if we have no routes with a PathPrefix of '/' or one with no definition at all
    // then add our own 'default catch all'
    if (
      !routes.find(item => get(item, 'Definition.Match.HTTP.PathPrefix') === '/') &&
      !routes.find(item => typeof item.Definition === 'undefined')
    ) {
      let nextNode;
      const resolverID = `resolver:${this.chain.ServiceName}.${this.chain.Namespace}.${this.chain.Datacenter}`;
      const splitterID = `splitter:${this.chain.ServiceName}.${this.chain.Namespace}`;
      if (typeof this.chain.Nodes[resolverID] !== 'undefined') {
        nextNode = resolverID;
      } else if (typeof this.chain.Nodes[splitterID] !== 'undefined') {
        nextNode = splitterID;
      }
      if (typeof nextNode !== 'undefined') {
        routes.push({
          Default: true,
          ID: `route:${this.chain.ServiceName}`,
          Name: this.chain.ServiceName,
          Definition: {
            Match: {
              HTTP: {
                PathPrefix: '/',
              },
            },
          },
          NextNode: nextNode,
        });
      }
    }
    return routes;
  }),
  resolvers: computed('chain.{Nodes,Targets}', function() {
    return getResolvers(
      this.chain.Datacenter,
      this.chain.Namespace,
      get(this, 'chain.Targets'),
      get(this, 'chain.Nodes')
    );
  }),
  graph: computed('splitters', 'routes', function() {
    const graph = this.dataStructs.graph();
    const router = this.chain.ServiceName;
    this.splitters.forEach(item => {
      item.Splits.forEach(splitter => {
        graph.addLink(item.ID, splitter.NextNode);
      });
    });
    this.routes.forEach((route, i) => {
      route = createRoute(route, router, this.dom.guid);
      graph.addLink(route.ID, route.NextNode);
    });
    return graph;
  }),
  selected: computed('selectedId', 'graph', function() {
    if (this.selectedId === '' || !this.dom.element(`#${this.selectedId}`)) {
      return {};
    }
    const id = this.selectedId;
    const type = id.split(':').shift();
    const nodes = [id];
    const edges = [];
    this.graph.forEachLinkedNode(id, (linkedNode, link) => {
      nodes.push(linkedNode.id);
      edges.push(`${link.fromId}>${link.toId}`);
      this.graph.forEachLinkedNode(linkedNode.id, (linkedNode, link) => {
        const nodeType = linkedNode.id.split(':').shift();
        if (type !== nodeType && type !== 'splitter' && nodeType !== 'splitter') {
          nodes.push(linkedNode.id);
          edges.push(`${link.fromId}>${link.toId}`);
        }
      });
    });
    return {
      nodes: nodes.map(item => `#${CSS.escape(item)}`),
      edges: edges.map(item => `#${CSS.escape(item)}`),
    };
  }),
  width: computed('isDisplayed', 'chain.{Nodes,Targets}', function() {
    return this.element.offsetWidth;
  }),
  height: computed('isDisplayed', 'chain.{Nodes,Targets}', function() {
    return this.element.offsetHeight;
  }),
  // TODO(octane): ember has trouble adding mouse events to svg elements whilst giving
  // the developer access to the mouse event therefore we just use JS to add our events
  // revisit this post Octane
  addPathListeners: function() {
    this._listeners.remove();
    this._listeners.add(this.dom.document(), {
      click: e => {
        // all route/splitter/resolver components currently
        // have classes that end in '-card'
        if (!this.dom.closest('[class$="-card"]', e.target)) {
          set(this, 'active', false);
          set(this, 'selectedId', '');
        }
      },
    });
    [...this.dom.elements('path.split', this.element)].forEach(item => {
      this._listeners.add(item, {
        mouseover: e => this.actions.showSplit.apply(this, [e]),
        mouseout: e => this.actions.hideSplit.apply(this, [e]),
      });
    });
    // TODO: currently don't think there is a way to listen
    // for an element being removed inside a component, possibly
    // using IntersectionObserver. It's a tiny detail, but we just always
    // remove the tooltip on component update as its so tiny, ideal
    // the tooltip would stay if there was no change to the <path>
    // set(this, 'activeTooltip', false);
  },
  actions: {
    showSplit: function(e) {
      this.setProperties({
        x: e.offsetX,
        y: e.offsetY - 5,
        tooltip: e.target.dataset.percentage,
        activeTooltip: true,
      });
{'fraction_non_alphanumeric': 0.16256597645148194, 'fraction_numerical': 0.0008120178643930166, 'mean_word_length': 2.623418652544866, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 38, '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': '4998114', 'n_tokens_mistral': 3764, 'n_tokens_neox': 3773, 'n_words': 814}