{#
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership.  The ASF licenses this file
 to you under the Apache License, Version 2.0 (the
 "License"); you may not use this file except in compliance
 with the License.  You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing,
 software distributed under the License is distributed on an
 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
#}

{% extends base_template %}
{% from 'appbuilder/loading_dots.html' import loading_dots %}

{% block page_title %}
  {% if search_query %}
    "{{ search_query }}" - DAGs - Airflow
  {% else %}
    DAGs - Airflow
  {% endif %}
{% endblock %}

{% block head_css %}
  {{ super() }}
  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('switch.css') }}">
  <link rel="stylesheet" type="text/css" href="{{ url_for_asset('dags.css') }}">
{% endblock %}

{% block content %}
  <h2>DAGs</h2>
  <div id="main_content">
    <div class="dags-table-wrap">
      <div class="row dags-table-header">
        <div class="col-md-4">
          <div class="form-group btn-group">
            <a href="{{ url_for('Airflow.index', status='all', search=request.args.get('search', None), tags=request.args.getlist('tags', None)) }}" class="btn {{'btn-primary' if status_filter == 'all' else 'btn-default'}}" title="Show active and paused DAGS">All <span class="badge">{{ "{:,}".format(status_count_all) }}</span></a>
            <a href="{{ url_for('Airflow.index', status='active', search=request.args.get('search', None), tags=request.args.getlist('tags', None)) }}" class="btn {{'btn-primary' if status_filter == 'active' else 'btn-default'}}" title="Show only active DAGS">Active <span class="badge">{{ "{:,}".format(status_count_active) }}</span></a>
            <a href="{{ url_for('Airflow.index', status='paused', search=request.args.get('search', None), tags=request.args.getlist('tags', None)) }}" class="btn {{'btn-primary' if status_filter == 'paused' else 'btn-default'}}" title="Show only paused DAGS">Paused <span class="badge">{{ "{:,}".format(status_count_paused) }}</span></a>
          </div>
        </div>
        <div class="col-sm-6 col-md-3">
          <form id="tags_form" style="width: 100%; text-align: left;">
            <div class="form-group search-input" style="width:100%;">
              <select multiple name="tags" id="tags_filter" class="select2-drop-mask" style="width: 100%;">
                {% for tag in tags %}
                  <option value="{{ tag.name }}" {% if tag.selected %}selected{% endif %}>{{ tag.name }}</option>
                {% endfor %}
              </select>
              {% if tags_filter|length > 0 %}
                <button type="reset" aria-label="Clear all tags" class="btn btn-default btn-sm material-icons search-input__clear-btn">cancel</button>
              {% endif %}
            </div>
          </form>
        </div>
        <div class="col-sm-6 col-md-3 col-md-offset-2">
          <form id="search_form">
            <div class="form-group search-input" style="width: 100%;">
              <label for="dag_query" class="sr-only">Search DAGs</label>
              <input type="search" id="dag_query" class="typeahead form-control search-input__input" data-provide="typeahead" style="width:100%;" value="{{search_query}}" autocomplete="off" placeholder="Search DAGs">
              {% if search_query %}
                <button type="reset" aria-label="Clear DAG Search Term" class="btn btn-default btn-sm material-icons search-input__clear-btn">cancel</button>
              {% endif %}
            </div>
          </form>
        </div>
      </div>
      <div class="dags-table-body">
        <table class="table table-striped table-bordered table-hover">
          <thead>
            <tr>
              <th width="12">
                <span class="material-icons text-muted js-tooltip" title="Use this toggle to pause a DAG. The scheduler won't schedule new tasks instances for a paused DAG. Tasks already running at pause time won't be affected.">info</span>
              </th>
              <th>DAG</th>
              <th>Owner</th>
              <th>Runs
                <span class="material-icons text-muted js-tooltip" aria-hidden="true" title="Status of all previous DAG runs.">info</span>
              </th>
              <th>Schedule</th>
              <th style="width:180px;">Last Run
                <span class="material-icons text-muted js-tooltip" aria-hidden="true" title="Execution Date/Time of Highest Dag Run.">info</span>
              </th>
              <th>Recent Tasks
                <span class="material-icons text-muted js-tooltip" aria-hidden="true" title="Status of tasks from all active DAG runs or, if not currently active, from most recent run.">info</span>
              </th>
              <th class="text-center" style="width:110px;">Actions</th>
              <th style="width:52px;">Links</th>
            </tr>
          </thead>
          <tbody>
            {% if dags|length == 0 %}
              <tr><td colspan="9">No results</td></tr>
            {% endif %}
            {% for dag in dags %}
              <tr>
                {# Column 1: Turn dag on/off #}
                <td style="padding-right:0;">
                  {% if dag.can_edit %}
                    {% set switch_tooltip = 'Pause/Unpause DAG' %}
                  {% else %}
                    {% set switch_tooltip = 'DAG is Paused' if dag.is_paused else 'DAG is Active' %}
                  {% endif %}
                  <label class="switch-label{{' disabled' if not dag.can_edit else ''  }} js-tooltip" title="{{ switch_tooltip }}">
                    <input class="switch-input"  id="toggle-{{ dag.dag_id }}" data-dag-id="{{ dag.dag_id }}" type="checkbox"
                           {{ " checked" if not dag.is_paused else "" }}
                           {{ " disabled" if not dag.can_edit else "" }}>
                    <span class="switch" aria-hidden="true"></span>
                  </label>
                </td>
                {# Column 2: Name #}
                <td>
                  <a href="{{ url_for('Airflow.'+ dag.get_default_view(), dag_id=dag.dag_id) }}"
                     title="{{ dag.description[0:80] + '…' if dag.description and dag.description|length > 80 else dag.description|default('', true) }}">
                    <strong>{{ dag.dag_id }}</strong>
                  </a>
                  <div>
                    {% for tag in dag.tags | sort(attribute='name') %}
                      <a class="label label-info"
                         href="?tags={{ tag.name }}"
                         style="margin: 6px 6px 0 0;">
                         {{ tag.name }}
                      </a>
                    {% endfor %}
                  </div>
                </td>
                {# Column 3: Dag Owners #}
                <td>
                  {% for owner in dag.owners.split(",") %}
                  <a class="label label-default"
                     href="?search={{ owner | trim }}"
                     style="margin: 3px 6px 3px 0;">
                     {{ owner | trim }}
                  </a>
                  {% endfor %}
                </td>
                {# Column 4: Dag Runs #}
                <td style="padding:0; width:120px;">
                  {{ loading_dots(classes='js-loading-dag-stats text-muted') }}
                  <svg height="10" width="10" id="dag-run-{{ dag.safe_dag_id }}" style="display: block;"></svg>
                </td>
                {# Column 5: Dag Schedule #}
                <td>
                  <a class="label label-default schedule" href="{{ url_for('DagRunModelView.list') }}?_flt_3_dag_id={{ dag.dag_id }}" data-dag-id="{{ dag.dag_id }}">
                    {{ dag.schedule_interval }}
                  </a>
                </td>
                {# Column 6: Last Run #}
                <td id="last-run-{{ dag.safe_dag_id }}" class="text-nowrap latest_dag_run">
                  {{ loading_dots(classes='js-loading-last-run text-muted') }}
                  <a></a>
                  <span aria-hidden="true" title=" " class="material-icons text-muted js-tooltip" style="display:none">info</span>
                </td>
                {# Column 7: Recent Tasks #}
                <td style="padding:0; width:323px; height:10px;">
                  {{ loading_dots(classes='js-loading-task-stats text-muted') }}
                  <svg height="10" width="10" id='task-run-{{ dag.safe_dag_id }}' style="display: block;"></svg>
                </td>
                {# Column 8: Actions #}
                <td class="text-center">
                  <div class="btn-group">
                    {% if dag %}
                      <a href="{{ url_for('Airflow.trigger', dag_id=dag.dag_id) }}" title="Trigger DAG" aria-label="Trigger DAG" class="btn btn-sm btn-default btn-icon-only">
                        <span class="material-icons" aria-hidden="true">play_arrow</span>
                      </a>
                      <a href="{{ url_for('Airflow.refresh', dag_id=dag.dag_id) }}" onclick="postAsForm(this.href); return false" title="Refresh DAG" aria-label="Refresh DAG" class="btn btn-sm btn-default btn-icon-only">
                        <span class="material-icons" aria-hidden="true">refresh</span>
                      </a>
                    {% endif %}
                    {# Use dag_id instead of dag.dag_id, because the DAG might not exist in the webserver's DagBag #}
                    <a href="{{ url_for('Airflow.delete', dag_id=dag.dag_id) }}" onclick="return confirmDeleteDag(this, '{{ dag.dag_id }}')" title="Delete&nbsp;DAG" aria-label="Delete DAG" class="btn btn-sm btn-default btn-icon-only">
                      <span class="material-icons text-danger" aria-hidden="true">delete_outline</span>
                    </a>
                  </div>
                </td>
                {# Column 9: Links #}
                <td class="dags-table-more">
                  {% if dag %}
                    <div class="dags-table-more__menu">
                      <div class="dags-table-more__links">
                        <a href="{{ url_for('Airflow.code', dag_id=dag.dag_id) }}" class="dags-table-more__link">
                          <span class="material-icons" aria-hidden="true">code</span>
                          Code
                        </a>
                        <a href="{{ url_for('Airflow.dag_details', dag_id=dag.dag_id) }}" class="dags-table-more__link">
                          <span class="material-icons" aria-hidden="true">details</span>
                          Details
                        </a>
                        <a href="{{ url_for('Airflow.gantt', dag_id=dag.dag_id) }}" class="dags-table-more__link">
                          <span class="material-icons" aria-hidden="true">vertical_distribute</span>
                          Gantt
                        </a>
                        <a href="{{ url_for('Airflow.landing_times', dag_id=dag.dag_id) }}" class="dags-table-more__link">
                          <span class="material-icons" aria-hidden="true">flight_land</span>
                          Landing
                        </a>
                        <a href="{{ url_for('Airflow.tries', dag_id=dag.dag_id) }}" class="dags-table-more__link">
                          <span class="material-icons" aria-hidden="true">repeat</span>
                          Tries
                        </a>
                        <a href="{{ url_for('Airflow.duration', dag_id=dag.dag_id) }}" class="dags-table-more__link">
                          <span class="material-icons" aria-hidden="true">hourglass_bottom</span>
                          Duration
                        </a>
                        <a href="{{ url_for('Airflow.graph', dag_id=dag.dag_id) }}" class="dags-table-more__link">
                          <span class="material-icons" aria-hidden="true">account_tree</span>
                          Graph
                        </a>
                        <a href="{{ url_for('Airflow.tree', dag_id=dag.dag_id, num_runs=num_runs) }}" class="dags-table-more__link">
                          <span class="material-icons" aria-hidden="true">nature</span>
                          Tree
                        </a>
                      </div>
                      <span class="dags-table-more__toggle"><span class="material-icons">more_horiz</span></span>
                    </div>
                  {% endif %}
                </td>
              </tr>
            {% endfor %}
          </tbody>
        </table>
      </div>
    </div>
    <div class="row">
      <div class="col-sm-6">
        {{paging}}
      </div>
      <div class="col-sm-6 text-right">
        Showing <strong>{{num_dag_from}}-{{num_dag_to}}</strong> of <strong>{{num_of_all_dags}}</strong> DAGs
      </div>
    </div>
  </div>
  <div id="svg-tooltip" class="tooltip top" style="position: fixed; display: none; opacity: 1; pointer-events: none;">
    <div class="tooltip-arrow"></div>
    <div class="tooltip-inner"></div>
  </div>
{% endblock %}

{% block tail %}
  {{ super() }}
  <script src="{{ url_for_asset('d3.min.js') }}"></script>
  <script>
    const DAGS_INDEX = '{{ url_for('Airflow.index') }}';
    const ENTER_KEY_CODE = 13;
    const STATE_COLOR = {{ state_color|tojson }};

    $('#tags_filter').select2({
      placeholder: 'Filter DAGs by tag',
      allowClear: true
    });

    $('#tags_filter').on('change', function (e) {
      e.preventDefault();
      var query = new URLSearchParams(window.location.search);
      if (!!e.val.length) {
        if (query.has('tags')) query.delete('tags');
        e.val.map(function(value) {
          query.append('tags', value);
        })
      } else {
        query.delete('tags');
        query.set('reset_tags', 'reset')
      }
      if (query.has('page')) query.delete('page');
      window.location = `${DAGS_INDEX}?${query.toString()}`;
    });

    $('#tags_form').on('reset', function(e) {
      e.preventDefault();
      var query = new URLSearchParams(window.location.search);
      query.delete('tags');
      if (query.has('page')) query.delete('page');
      query.set('reset_tags', 'reset')
      window.location = `${DAGS_INDEX}?${query.toString()}`;
    });

    $('#dag_query').on('keypress', function (e) {
      // check for key press on ENTER (key code 13) to trigger the search
      if (e.which === ENTER_KEY_CODE) {
        var query = new URLSearchParams(window.location.search);
        query.set("search", e.target.value.trim());
        query.delete("page");
        window.location = DAGS_INDEX + "?" + query.toString();
        e.preventDefault();
      }
    });

    $('#page_size').on('change', function() {
      p_size = $(this).val();
      window.location = DAGS_INDEX + "?page_size=" + p_size;
    });

    function confirmDeleteDag(link, dag_id){
      if (confirm("Are you sure you want to delete '"+dag_id+"' now?\n\
        This option will delete ALL metadata, DAG runs, etc.\n\
        EXCEPT Log.\n\
        This cannot be undone.")) {
        postAsForm(link.href, {});
      }
      return false;
    }

    var encoded_dag_ids = new URLSearchParams();

    $.each($('[id^=toggle]'), function() {
      var $input = $(this);
      var dagId = $input.data('dag-id');
      encoded_dag_ids.append('dag_ids', dagId);

      $input.change(function() {
        var isPaused = $input.is(':checked');
        var url = `{{ url_for('Airflow.paused') }}?is_paused=${isPaused}&dag_id=${encodeURIComponent(dagId)}`;
        $input.removeClass('switch-input--error');
        $.post(url).fail(function() {
          setTimeout(() => {
            $input.prop('checked', !isPaused);
            $input.addClass('switch-input--error');
          }, 500 );
        });
      });
    });

    $('.typeahead').typeahead({
      source: function (query, callback) {
        return $.ajax('{{ url_for('DagModelView.autocomplete') }}',
          {
            data: {
              'query': encodeURIComponent(query),
              'status': '{{ status_filter }}'
            },
            success: callback
          });
      },
      autoSelect: false,
      afterSelect: function(value) {
        var search_query = value.trim();
        if (search_query) {
          var query = new URLSearchParams(window.location.search);
          query.set('search', search_query);
          window.location = DAGS_INDEX + "?" + query;
        }
      }
    });

    $('#search_form').on('reset', function() {
      var query = new URLSearchParams(window.location.search);
      query.delete('search');
      query.delete('page');
      window.location = DAGS_INDEX + "?" + query;
    });

    $('#main_content').show(250);
    diameter = 25;
    circle_margin = 4;
    stroke_width = 2;
    stroke_width_hover = 6;

    function blockedHandler(error, json) {
      $.each(json, function() {
        a = document.querySelector(`[data-dag-id="${this.dag_id}"]`);
        a.title = `${this.active_dag_run}/${this.max_active_runs} active dag runs`;
        if(this.active_dag_run >= this.max_active_runs) {
          a.style.color = '#e43921';
        }
      });
    }

    function lastDagRunsHandler(error, json) {
      for(var safe_dag_id in json) {
        dag_id = json[safe_dag_id].dag_id;
        execution_date = json[safe_dag_id].execution_date;
        start_date = json[safe_dag_id].start_date;
        g = d3.select('#last-run-' + safe_dag_id)
        g.selectAll('a')
          .attr('href', '{{ url_for('Airflow.graph') }}?dag_id=' + encodeURIComponent(dag_id) + '&execution_date=' + encodeURIComponent(execution_date))
          .insert(isoDateToTimeEl.bind(null, execution_date, {title: false}));
        g.selectAll('span')
          // We don't translate the timezone in the tooltip, that stays in UTC.
          .attr('data-original-title', 'Start Date: ' + start_date)
          .style('display', null);
        g.selectAll('.js-loading-last-run').remove();
      }
      $('.js-loading-last-run').remove();
    }

    function drawDagStatsForDag(dag_id, stats) {
      g = d3.select('svg#dag-run-' + dag_id.replace(/\./g, '__dot__'))
        .attr('height', diameter + (stroke_width_hover * 2))
        .attr('width', '110px')
        .selectAll("g")
        .data(states)
        .enter()
        .append('g')
        .attr('transform', function(d, i) {
          x = (i * (diameter + circle_margin)) + (diameter/2 + circle_margin);
          y = (diameter/2) + stroke_width_hover;
          return 'translate(' + x + ',' + y + ')';
        });

      g.append('svg:a')
        .attr('href', function(d) {
          return '{{ url_for('DagRunModelView.list') }}?_flt_3_dag_id=' + dag_id + '&_flt_3_state=' + d.state;
        })
      .append('circle')
        .attr('id', function(d) {return 'run-' + dag_id.replace(/\./g, '_') + d.state || 'none'})
        .attr('class', 'has-svg-tooltip')
        .attr('stroke-width', function(d) {
          if (d.count > 0)
            return stroke_width;
          else {
            return 1;
          }
        })
        .attr('stroke', function(d) {
          if (d.count > 0)
            return STATE_COLOR[d.state];
          else {
            return 'gainsboro';
          }
        })
        .attr('fill', '#fff')
        .attr('r', diameter/2)
        .attr('title', function(d) {return d.state})
        .on('mouseover', function(d, i) {
          if (d.count > 0) {
            d3.select(this).transition().duration(400)
              .attr('fill', '#e2e2e2')
              .style('stroke-width', stroke_width_hover);
          }
        })
        .on('mouseout', function(d, i) {
          if (d.count > 0) {
            d3.select(this).transition().duration(400)
              .attr('fill', '#fff')
              .style('stroke-width', stroke_width);
          }
        })
        .style('opacity', 0)
        .transition().duration(300).delay(function(d, i){return i*50;})
        .style('opacity', 1);
      d3.select('.js-loading-dag-stats').remove();

      g.append('text')
        .attr('fill', '#51504f')
        .attr('text-anchor', 'middle')
        .attr('vertical-align', 'middle')
        .attr('font-size', 8)
        .attr('y', 3)
        .style('pointer-events', 'none')
        .text(function(d){ return d.count > 0 ? d.count : ''; });
    }

    function dagStatsHandler(error, json) {
      for(var dag_id in json) {
        states = json[dag_id];
        drawDagStatsForDag(dag_id, states);
      }
    }

    function drawTaskStatsForDag(dag_id, states) {
      g = d3.select('svg#task-run-' + dag_id.replace(/\./g, '__dot__'))
        .attr('height', diameter + (stroke_width_hover * 2))
        .attr('width', (states.length * (diameter + circle_margin)) + circle_margin)
        .selectAll("g")
        .data(states)
        .enter()
        .append('g')
        .attr('transform', function(d, i) {
          x = (i * (diameter + circle_margin)) + (diameter/2 + circle_margin);
          y = (diameter/2) + stroke_width_hover;
          return 'translate(' + x + ',' + y + ')';
        });

      g.append('svg:a')
        .attr('href', function(d) {
          return '{{ url_for('TaskInstanceModelView.list') }}?_flt_3_dag_id=' + dag_id + '&_flt_3_state=' + d.state;
        })
      .append('circle')
        .attr('id', function(d) {return 'task-' + dag_id.replace(/\./g, '_') + d.state || 'none'})
        .attr('class', 'has-svg-tooltip')
        .attr('stroke-width', function(d) {
          if (d.count > 0)
            return stroke_width;
          else {
            return 1;
          }
        })
        .attr('stroke', function(d) {
          if (d.count > 0)
            return STATE_COLOR[d.state];
          else {
            return 'gainsboro';
          }
        })
        .attr('fill', '#fff')
        .attr('r', diameter/2)
        .attr('title', function(d) {return d.state || 'none'})
        .on('mouseover', function(d, i) {
          if (d.count > 0) {
            d3.select(this).transition().duration(400)
              .attr('fill', '#e2e2e2')
              .style('stroke-width', stroke_width_hover);
          }
        })
        .on('mouseout', function(d, i) {
          if (d.count > 0) {
            d3.select(this).transition().duration(400)
              .attr('fill', '#fff')
              .style('stroke-width', stroke_width);
          }
        })
        .style('opacity', 0)
        .transition().duration(300).delay(function(d, i){return i*50;})
        .style('opacity', 1);
      d3.select('.js-loading-task-stats').remove();

      g.append('text')
        .attr('fill', '#51504f')
        .attr('text-anchor', 'middle')
        .attr('vertical-align', 'middle')
        .attr('font-size', 8)
        .attr('y', 3)
        .style('pointer-events', 'none')
        .text(function(d){ return d.count > 0 ? d.count : ''; });
    }

    function taskStatsHandler(error, json) {
      for(var dag_id in json) {
        states = json[dag_id];
        drawTaskStatsForDag(dag_id, states);
      }
    }

    if (encoded_dag_ids.has('dag_ids')) {
      // dags on page fetch stats
      d3.json('{{ url_for('Airflow.blocked') }}')
        .header('X-CSRFToken', '{{ csrf_token() }}')
        .post(encoded_dag_ids, blockedHandler);
      d3.json('{{ url_for('Airflow.last_dagruns') }}')
        .header('X-CSRFToken', '{{ csrf_token() }}')
        .post(encoded_dag_ids, lastDagRunsHandler);
      d3.json('{{ url_for('Airflow.dag_stats') }}')
        .header('X-CSRFToken', '{{ csrf_token() }}')
        .post(encoded_dag_ids, dagStatsHandler);
      d3.json('{{ url_for('Airflow.task_stats') }}')
        .header('X-CSRFToken', '{{ csrf_token() }}')
        .post(encoded_dag_ids, taskStatsHandler);
    }
    else {
      // no dags, hide the loading dots
      $('.js-loading-task-stats').remove();
      $('.js-loading-dag-stats').remove();
    }

    function showSvgTooltip(text, circ) {
      var tip = $('#svg-tooltip');
      tip.children('.tooltip-inner').text(text);
      var centeringOffset = tip.width() / 2;
      tip.css({
        'display': 'block',
        'left': circ.left + 12.5 - centeringOffset + 'px',// 12.5 == half of circle width
        'top': circ.top - 25 + 'px'// 25 == position above circle
      });
    }

    function hideSvgTooltip() {
      $('#svg-tooltip').css('display', 'none');
    }

    $(window).on('load', function() {
      $('body').on('mouseover', '.has-svg-tooltip', function(e) {
        var elem = e.target;
        var text = elem.getAttribute('title');
        var circ = elem.getBoundingClientRect();
        showSvgTooltip(text, circ);
      });

      $('body').on('mouseout', '.has-svg-tooltip', function(e) {
        hideSvgTooltip();
      });
    });
  </script>
{% endblock %}
