schedule-options.js

612 lines | 17.841 kB Blame History Raw Download
/*
 * Copyright 2012 LinkedIn Corp.
 *
 * Licensed 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.
 */

$.namespace('azkaban');

var scheduleCustomSvgGraphView;
var scheduleCustomJobListView;

var scheduleFlowView;

var scheduleFlowData;

//function recurseAllAncestors(nodes, disabledMap, id, disable) {
//  var node = nodes[id];
//
//  if (node.inNodes) {
//    for (var key in node.inNodes) {
//      disabledMap[key] = disable;
//      recurseAllAncestors(nodes, disabledMap, key, disable);
//    }
//  }
//}
//
//function recurseAllDescendents(nodes, disabledMap, id, disable) {
//  var node = nodes[id];
//
//  if (node.outNodes) {
//    for (var key in node.outNodes) {
//      disabledMap[key] = disable;
//      recurseAllDescendents(nodes, disabledMap, key, disable);
//    }
//  }
//}
//
azkaban.ScheduleContextMenu = Backbone.View.extend({
  events: {
    "click #scheduleDisableArrow": "handleDisabledClick",
    "click #scheduleEnableArrow": "handleEnabledClick"
  },
  initialize: function (settings) {
    $('#scheduleDisableSub').hide();
    $('#scheduleEnableSub').hide();
  },
  handleEnabledClick: function (evt) {
    if (evt.stopPropagation) {
      evt.stopPropagation();
    }
    evt.cancelBubble = true;

    if (evt.currentTarget.expanded) {
      evt.currentTarget.expanded = false;
      $('#scheduleEnableArrow').removeClass('collapse');
      $('#scheduleEnableSub').hide();
    }
    else {
      evt.currentTarget.expanded = true;
      $('#scheduleEnableArrow').addClass('collapse');
      $('#scheduleEnableSub').show();
    }
  },
  handleDisabledClick: function (evt) {
    if (evt.stopPropagation) {
      evt.stopPropagation();
    }
    evt.cancelBubble = true;

    if (evt.currentTarget.expanded) {
      evt.currentTarget.expanded = false;
      $('#scheduleDisableArrow').removeClass('collapse');
      $('#scheduleDisableSub').hide();
    }
    else {
      evt.currentTarget.expanded = true;
      $('#scheduleDisableArrow').addClass('collapse');
      $('#scheduleDisableSub').show();
    }
  }
});

azkaban.ScheduleFlowView = Backbone.View.extend({
  events: {
    "click #schedule-btn": "handleScheduleFlow",
    "click #adv-schedule-opt-btn": "handleAdvancedSchedule"
  },
  initialize: function (settings) {
    $("#datepicker").datepicker();
    $("#datepicker").datepicker('setDate', new Date());
    $("#errorMsg").hide();
  },
  handleAdvancedSchedule: function (evt) {
    console.log("Clicked advanced schedule options button");
    //$('#confirm-container').hide();
    $.modal.close();
    advancedScheduleView.show();
  },
  handleScheduleFlow: function (evt) {

    var hourVal = $('#hour').val();
    var minutesVal = $('#minutes').val();
    var ampmVal = $('#am_pm').val();
    var timezoneVal = $('#timezone').val();
    var dateVal = $('#datepicker').val();
    var is_recurringVal = $('#is_recurring').val();
    var periodVal = $('#period').val();
    var periodUnits = $('#period_units').val();

    console.log("Creating schedule for " + projectName + "." + flowName);
    $.ajax({
      async: "false",
      url: "schedule",
      dataType: "json",
      type: "POST",
      data: {
        action: "scheduleFlow",
        projectId: projectId,
        projectName: projectName,
        flowName: flowName,
        hour: hourVal,
        minutes: minutesVal,
        am_pm: ampmVal,
        timezone: timezoneVal,
        date: dateVal,
        userExec: "dummy",
        is_recurring: is_recurringVal,
        period: periodVal,
        period_units: periodUnits
      },
      success: function (data) {
        if (data.status == "success") {
          console.log("Successfully scheduled for " + projectName + "."
              + flowName);
          if (data.action == "redirect") {
            window.location = contextURL + "/manager?project=" + projectName
                + "&flow=" + flowName;
          }
          else {
            $("#success_message").text("Flow " + projectName + "." + flowName
                + " scheduled!");
            window.location = contextURL + "/manager?project=" + projectName
                + "&flow=" + flowName;
          }
        }
        else {
          if (data.action == "login") {
            window.location = "";
          }
          else {
            $("#errorMsg").text("ERROR: " + data.message);
            $("#errorMsg").slideDown("fast");
          }
        }
      }
    });

  },
  render: function () {
  }
});

azkaban.AdvancedScheduleView = Backbone.View.extend({
  events: {
    "click": "closeEditingTarget",
    "click #adv-schedule-btn": "handleAdvSchedule",
    "click #schedule-cancel-btn": "handleCancel",
    "click .modal-close": "handleCancel",
    "click #scheduleGeneralOptions": "handleGeneralOptionsSelect",
    "click #scheduleFlowOptions": "handleFlowOptionsSelect",
    "click #scheduleAddRow": "handleAddRow",
    "click table .editable": "handleEditColumn",
    "click table .removeIcon": "handleRemoveColumn"
  },
  initialize: function (setting) {
    this.contextMenu = new azkaban.ScheduleContextMenu(
        {el: $('#scheduleDisableJobMenu')});
    this.handleGeneralOptionsSelect();
    $("#advdatepicker").datepicker();
    $("#advdatepicker").datepicker('setDate', new Date());
  },
  show: function () {
    $('#scheduleModalBackground').show();
    $('#schedule-options').show();
    this.handleGeneralOptionsSelect();

    scheduleFlowData = this.model.clone();
    this.flowData = scheduleFlowData;
    var flowData = scheduleFlowData;

    var fetchData = {
      "project": projectName,
      "ajax": "flowInfo",
      "flow": flowName
    };

    var executeURL = contextURL + "/executor";
    this.executeURL = executeURL;
    var scheduleURL = contextURL + "/schedule";
    this.scheduleURL = scheduleURL;
    var handleAddRow = this.handleAddRow;

    var data = flowData.get("data");
    var nodes = {};
    for (var i = 0; i < data.nodes.length; ++i) {
      var node = data.nodes[i];
      nodes[node.id] = node;
    }

    for (var i = 0; i < data.edges.length; ++i) {
      var edge = data.edges[i];
      var fromNode = nodes[edge.from];
      var toNode = nodes[edge.target];

      if (!fromNode.outNodes) {
        fromNode.outNodes = {};
      }
      fromNode.outNodes[toNode.id] = toNode;

      if (!toNode.inNodes) {
        toNode.inNodes = {};
      }
      toNode.inNodes[fromNode.id] = fromNode;
    }
    flowData.set({nodes: nodes});

    var disabled = {};
    for (var i = 0; i < data.nodes.length; ++i) {
      var updateNode = data.nodes[i];
      if (updateNode.status == "DISABLED" || updateNode.status == "SKIPPED") {
        updateNode.status = "READY";
        disabled[updateNode.id] = true;
      }
      if (updateNode.status == "SUCCEEDED" || updateNode.status == "RUNNING") {
        disabled[updateNode.id] = true;
      }
    }
    flowData.set({disabled: disabled});

    $.get(
        executeURL,
        fetchData,
        function (data) {
          if (data.error) {
            alert(data.error);
          }
          else {
            if (data.successEmails) {
              $('#scheduleSuccessEmails').val(data.successEmails.join());
            }
            if (data.failureEmails) {
              $('#scheduleFailureEmails').val(data.failureEmails.join());
            }

            if (data.failureAction) {
              $('#scheduleFailureAction').val(data.failureAction);
            }
            if (data.notifyFailureFirst) {
              $('#scheduleNotifyFailureFirst').attr('checked', true);
            }
            if (data.notifyFailureLast) {
              $('#scheduleNotifyFailureLast').attr('checked', true);
            }
            if (data.flowParam) {
              var flowParam = data.flowParam;
              for (var key in flowParam) {
                var row = handleAddRow();
                var td = $(row).find('td');
                $(td[0]).text(key);
                $(td[1]).text(flowParam[key]);
              }
            }

            if (!data.running || data.running.length == 0) {
              $(".radio").attr("disabled", "disabled");
              $(".radioLabel").addClass("disabled", "disabled");
            }
          }
        },
        "json"
    );
  },
  handleCancel: function (evt) {
    $('#scheduleModalBackground').hide();
    $('#schedule-options').hide();
  },
  handleGeneralOptionsSelect: function (evt) {
    $('#scheduleFlowOptions').removeClass('selected');
    $('#scheduleGeneralOptions').addClass('selected');

    $('#scheduleGeneralPanel').show();
    $('#scheduleGraphPanel').hide();
  },
  handleFlowOptionsSelect: function (evt) {
    $('#scheduleGeneralOptions').removeClass('selected');
    $('#scheduleFlowOptions').addClass('selected');

    $('#scheduleGraphPanel').show();
    $('#scheduleGeneralPanel').hide();

    if (this.flowSetup) {
      return;
    }

    scheduleCustomSvgGraphView = new azkaban.SvgGraphView({
      el: $('#scheduleSvgDivCustom'),
      model: scheduleFlowData,
      rightClick: {
        id: 'scheduleDisableJobMenu',
        callback: this.handleDisableMenuClick
      }
    });
    scheduleCustomJobsListView = new azkaban.JobListView({
      el: $('#scheduleJobListCustom'),
      model: scheduleFlowData,
      rightClick: {
        id: 'scheduleDisableJobMenu',
        callback: this.handleDisableMenuClick
      }
    });
    scheduleFlowData.trigger("change:graph");

    this.flowSetup = true;
  },
  handleAdvSchedule: function (evt) {
    var scheduleURL = this.scheduleURL;
    var disabled = this.flowData.get("disabled");
    var disabledJobs = "";
    for (var job in disabled) {
      if (disabled[job] == true) {
        disabledJobs += "," + job;
      }
    }
    var failureAction = $('#scheduleFailureAction').val();
    var failureEmails = $('#scheduleFailureEmails').val();
    var successEmails = $('#scheduleSuccessEmails').val();
    var notifyFailureFirst = $('#scheduleNotifyFailureFirst').is(':checked');
    var notifyFailureLast = $('#scheduleNotifyFailureLast').is(':checked');
    var executingJobOption = $('input:radio[name=gender]:checked').val();

    var scheduleTime = $('#advhour').val() + "," + $('#advminutes').val() + ","
        + $('#advam_pm').val() + "," + $('#advtimezone').val();
    var scheduleDate = $('#advdatepicker').val();
    var is_recurring = $('#advis_recurring').val();
    var period = $('#advperiod').val() + $('#advperiod_units').val();

    var flowOverride = {};
    var editRows = $(".editRow");
    for (var i = 0; i < editRows.length; ++i) {
      var row = editRows[i];
      var td = $(row).find('td');
      var key = $(td[0]).text();
      var val = $(td[1]).text();

      if (key && key.length > 0) {
        flowOverride[key] = val;
      }
    }

    var scheduleData = {
      projectId: projectId,
      projectName: projectName,
      ajax: "advSchedule",
      flowName: flowName,
      scheduleTime: scheduleTime,
      scheduleDate: scheduleDate,
      is_recurring: is_recurring,
      period: period,
      disabledJobs: disabledJobs,
      failureAction: failureAction,
      failureEmails: failureEmails,
      successEmails: successEmails,
      notifyFailureFirst: notifyFailureFirst,
      notifyFailureLast: notifyFailureLast,
      executingJobOption: executingJobOption,
      flowOverride: flowOverride
    };

    $.post(
        scheduleURL,
        scheduleData,
        function (data) {
          if (data.error) {
            alert(data.error);
          }
          else {
            window.location = scheduleURL;
          }
        },
        "json"
    )
  },
  handleAddRow: function (evt) {
    var tr = document.createElement("tr");
    var tdName = document.createElement("td");
    var tdValue = document.createElement("td");

    var icon = document.createElement("span");
    $(icon).addClass("removeIcon");
    var nameData = document.createElement("span");
    $(nameData).addClass("spanValue");
    var valueData = document.createElement("span");
    $(valueData).addClass("spanValue");

    $(tdName).append(icon);
    $(tdName).append(nameData);
    $(tdName).addClass("name");
    $(tdName).addClass("editable");

    $(tdValue).append(valueData);
    $(tdValue).addClass("editable");

    $(tr).addClass("editRow");
    $(tr).append(tdName);
    $(tr).append(tdValue);

    $(tr).insertBefore("#scheduleAddRow");
    return tr;
  },
  handleEditColumn: function (evt) {
    var curTarget = evt.currentTarget;

    if (this.editingTarget != curTarget) {
      this.closeEditingTarget();

      var text = $(curTarget).children(".spanValue").text();
      $(curTarget).empty();

      var input = document.createElement("input");
      $(input).attr("type", "text");
      $(input).css("width", "100%");
      $(input).val(text);
      $(curTarget).addClass("editing");
      $(curTarget).append(input);
      $(input).focus();
      this.editingTarget = curTarget;
    }
  },
  handleRemoveColumn: function (evt) {
    var curTarget = evt.currentTarget;
    // Should be the table
    var row = curTarget.parentElement.parentElement;
    $(row).remove();
  },
  closeEditingTarget: function (evt) {
    if (this.editingTarget != null && this.editingTarget != evt.target
        && this.editingTarget != evt.target.parentElement) {
      var input = $(this.editingTarget).children("input")[0];
      var text = $(input).val();
      $(input).remove();

      var valueData = document.createElement("span");
      $(valueData).addClass("spanValue");
      $(valueData).text(text);

      if ($(this.editingTarget).hasClass("name")) {
        var icon = document.createElement("span");
        $(icon).addClass("removeIcon");
        $(this.editingTarget).append(icon);
      }

      $(this.editingTarget).removeClass("editing");
      $(this.editingTarget).append(valueData);
      this.editingTarget = null;
    }
  },
  handleDisableMenuClick: function (action, el, pos) {
    var flowData = scheduleFlowData;
    var jobid = el[0].jobid;
    var requestURL = contextURL + "/manager?project=" + projectName + "&flow="
        + flowName + "&job=" + jobid;
    if (action == "open") {
      window.location.href = requestURL;
    }
    else if (action == "openwindow") {
      window.open(requestURL);
    }
    else if (action == "disable") {
      var disabled = flowData.get("disabled");

      disabled[jobid] = true;
      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
    else if (action == "disableAll") {
      var disabled = flowData.get("disabled");

      var nodes = flowData.get("nodes");
      for (var key in nodes) {
        disabled[key] = true;
      }

      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
    else if (action == "disableParents") {
      var disabled = flowData.get("disabled");
      var nodes = flowData.get("nodes");
      var inNodes = nodes[jobid].inNodes;

      if (inNodes) {
        for (var key in inNodes) {
          disabled[key] = true;
        }
      }

      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
    else if (action == "disableChildren") {
      var disabledMap = flowData.get("disabled");
      var nodes = flowData.get("nodes");
      var outNodes = nodes[jobid].outNodes;

      if (outNodes) {
        for (var key in outNodes) {
          disabledMap[key] = true;
        }
      }

      flowData.set({disabled: disabledMap});
      flowData.trigger("change:disabled");
    }
    else if (action == "disableAncestors") {
      var disabled = flowData.get("disabled");
      var nodes = flowData.get("nodes");

      recurseAllAncestors(nodes, disabled, jobid, true);

      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
    else if (action == "disableDescendents") {
      var disabled = flowData.get("disabled");
      var nodes = flowData.get("nodes");

      recurseAllDescendents(nodes, disabled, jobid, true);

      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
    else if (action == "enable") {
      var disabled = flowData.get("disabled");

      disabled[jobid] = false;
      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
    else if (action == "enableAll") {
      disabled = {};
      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
    else if (action == "enableParents") {
      var disabled = flowData.get("disabled");
      var nodes = flowData.get("nodes");
      var inNodes = nodes[jobid].inNodes;

      if (inNodes) {
        for (var key in inNodes) {
          disabled[key] = false;
        }
      }

      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
    else if (action == "enableChildren") {
      var disabled = flowData.get("disabled");
      var nodes = flowData.get("nodes");
      var outNodes = nodes[jobid].outNodes;

      if (outNodes) {
        for (var key in outNodes) {
          disabled[key] = false;
        }
      }

      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
    else if (action == "enableAncestors") {
      var disabled = flowData.get("disabled");
      var nodes = flowData.get("nodes");

      recurseAllAncestors(nodes, disabled, jobid, false);

      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
    else if (action == "enableDescendents") {
      var disabled = flowData.get("disabled");
      var nodes = flowData.get("nodes");

      recurseAllDescendents(nodes, disabled, jobid, false);

      flowData.set({disabled: disabled});
      flowData.trigger("change:disabled");
    }
  }
});