azkaban-developers

Details

diff --git a/azkaban-common/src/main/java/azkaban/project/ProjectLogEvent.java b/azkaban-common/src/main/java/azkaban/project/ProjectLogEvent.java
index 5512291..1e78168 100644
--- a/azkaban-common/src/main/java/azkaban/project/ProjectLogEvent.java
+++ b/azkaban-common/src/main/java/azkaban/project/ProjectLogEvent.java
@@ -33,7 +33,8 @@ public class ProjectLogEvent {
     SCHEDULE(7),
     SLA(8),
     PROXY_USER(9),
-    PURGE(10);
+    PURGE(10),
+    PROPERTY_OVERRIDE(11);
 
     private int numVal;
 
@@ -47,30 +48,32 @@ public class ProjectLogEvent {
 
     public static EventType fromInteger(int x) {
       switch (x) {
-      case 1:
-        return CREATED;
-      case 2:
-        return DELETED;
-      case 3:
-        return USER_PERMISSION;
-      case 4:
-        return GROUP_PERMISSION;
-      case 5:
-        return DESCRIPTION;
-      case 6:
-        return UPLOADED;
-      case 7:
-        return SCHEDULE;
-      case 8:
-        return SLA;
-      case 9:
-        return PROXY_USER;
-      case 10:
-        return PURGE;
-      case 128:
-        return ERROR;
-      default:
-        return ERROR;
+        case 1:
+          return CREATED;
+        case 2:
+          return DELETED;
+        case 3:
+          return USER_PERMISSION;
+        case 4:
+          return GROUP_PERMISSION;
+        case 5:
+          return DESCRIPTION;
+        case 6:
+          return UPLOADED;
+        case 7:
+          return SCHEDULE;
+        case 8:
+          return SLA;
+        case 9:
+          return PROXY_USER;
+        case 10:
+          return PURGE;
+        case 11:
+          return PROPERTY_OVERRIDE;
+        case 128:
+          return ERROR;
+        default:
+          return ERROR;
       }
     }
   }
diff --git a/azkaban-common/src/main/java/azkaban/project/ProjectManager.java b/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
index a069c29..b079bbd 100644
--- a/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
+++ b/azkaban-common/src/main/java/azkaban/project/ProjectManager.java
@@ -32,9 +32,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.log4j.Logger;
 
 import azkaban.flow.Flow;
-import azkaban.project.DirectoryFlowLoader;
 import azkaban.project.ProjectLogEvent.EventType;
-import azkaban.project.ProjectWhitelist.WhitelistType;
 import azkaban.project.validator.ValidationReport;
 import azkaban.project.validator.ValidationStatus;
 import azkaban.project.validator.ValidatorConfigs;
@@ -44,6 +42,7 @@ import azkaban.user.Permission;
 import azkaban.user.Permission.Type;
 import azkaban.user.User;
 import azkaban.utils.Props;
+import azkaban.utils.PropsUtils;
 import azkaban.utils.Utils;
 
 public class ProjectManager {
@@ -354,16 +353,22 @@ public class ProjectManager {
     return projectLoader.fetchProjectProperty(project, jobName + ".jor");
   }
 
-  public void setJobOverrideProperty(Project project, Props prop, String jobName)
+  public void setJobOverrideProperty(Project project, Props prop, String jobName, User modifier)
       throws ProjectManagerException {
     prop.setSource(jobName + ".jor");
     Props oldProps =
         projectLoader.fetchProjectProperty(project, prop.getSource());
+
     if (oldProps == null) {
       projectLoader.uploadProjectProperty(project, prop);
     } else {
       projectLoader.updateProjectProperty(project, prop);
     }
+
+    String diffMessage = PropsUtils.getPropertyDiff(oldProps, prop);
+
+    projectLoader.postEvent(project, EventType.PROPERTY_OVERRIDE,
+        modifier.getUserId(), diffMessage);
     return;
   }
 
diff --git a/azkaban-common/src/main/java/azkaban/utils/PropsUtils.java b/azkaban-common/src/main/java/azkaban/utils/PropsUtils.java
index c013c4b..9d5171b 100644
--- a/azkaban-common/src/main/java/azkaban/utils/PropsUtils.java
+++ b/azkaban-common/src/main/java/azkaban/utils/PropsUtils.java
@@ -37,6 +37,9 @@ import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 import org.joda.time.DateTime;
 
+import com.google.common.collect.Maps;
+import com.google.common.collect.MapDifference;
+
 import azkaban.executor.ExecutableFlowBase;
 import azkaban.flow.CommonJobProperties;
 
@@ -365,4 +368,44 @@ public class PropsUtils {
 
     return propsMap;
   }
+
+  /**
+   * @param oldProps
+   * @param newProps
+   * @return the difference between oldProps and newProps.
+   */
+  public static String getPropertyDiff(Props oldProps, Props newProps) {
+
+    StringBuilder builder = new StringBuilder("");
+
+    MapDifference<String, String> md =
+        Maps.difference(toStringMap(oldProps, false), toStringMap(newProps, false));
+
+    Map<String, String> newlyCreatedProperty = md.entriesOnlyOnRight();
+    if (newlyCreatedProperty != null && newlyCreatedProperty.size() > 0) {
+      builder.append("Newly created Properties: ");
+      newlyCreatedProperty.forEach((k, v) -> {
+        builder.append("[ " + k + ", " + v + "], ");
+      });
+      builder.append("\n");
+    }
+
+    Map<String, String> deletedProperty = md.entriesOnlyOnLeft();
+    if (deletedProperty != null && deletedProperty.size() > 0) {
+      builder.append("Deleted Properties: ");
+      deletedProperty.forEach((k, v) -> {
+        builder.append("[ " + k + ", " + v + "], ");
+      });
+      builder.append("\n");
+    }
+
+    Map<String, MapDifference.ValueDifference<String>> diffProperties = md.entriesDiffering();
+    if (diffProperties != null && diffProperties.size() > 0) {
+      builder.append("Modified Properties: ");
+      diffProperties.forEach((k, v) -> {
+        builder.append("[ " + k + ", " + v.leftValue() + "-->" + v.rightValue() + "], ");
+      });
+    }
+    return builder.toString();
+  }
 }
diff --git a/azkaban-common/src/test/java/azkaban/utils/PropsUtilsTest.java b/azkaban-common/src/test/java/azkaban/utils/PropsUtilsTest.java
index 54464cd..3201d65 100644
--- a/azkaban-common/src/test/java/azkaban/utils/PropsUtilsTest.java
+++ b/azkaban-common/src/test/java/azkaban/utils/PropsUtilsTest.java
@@ -208,6 +208,37 @@ public class PropsUtilsTest {
     failIfNotException(props);
   }
 
+  @Test
+  public void testGetPropertyDiff() throws IOException {
+    Props oldProps = new Props();
+    Props newProps1 = new Props();
+
+    oldProps.put("a", "a_value1");
+    oldProps.put("b", "b_value1");
+
+    newProps1.put("b", "b_value2");
+
+    String message1 = PropsUtils.getPropertyDiff(oldProps, newProps1);
+    Assert.assertEquals(message1, "Deleted Properties: [ a, a_value1], \nModified Properties: [ b, b_value1-->b_value2], ");
+
+    Props newProps2 = new Props();
+
+    newProps2.put("a", "a_value1");
+    newProps2.put("b", "b_value1");
+    newProps2.put("c", "c_value1");
+
+    String message2 = PropsUtils.getPropertyDiff(oldProps, newProps2);
+    Assert.assertEquals(message2, "Newly created Properties: [ c, c_value1], \n");
+
+    Props newProps3 = new Props();
+
+    newProps3.put("b", "b_value1");
+    newProps3.put("c", "a_value1");
+
+    String message3 = PropsUtils.getPropertyDiff(oldProps, newProps3);
+    Assert.assertEquals(message3, "Newly created Properties: [ c, a_value1], \nDeleted Properties: [ a, a_value1], \n");
+  }
+
   private void failIfNotException(Props props) {
     try {
       PropsUtils.resolveProps(props);
diff --git a/azkaban-web-server/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/azkaban-web-server/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 685cc80..2ba49ef 100644
--- a/azkaban-web-server/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/azkaban-web-server/src/main/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -25,7 +25,6 @@ import java.io.OutputStream;
 import java.io.Writer;
 import java.security.AccessControlException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
@@ -298,7 +297,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
         }
       } else if (ajaxName.equals("setJobOverrideProperty")) {
         if (handleAjaxPermission(project, user, Type.WRITE, ret)) {
-          ajaxSetJobOverrideProperty(project, ret, req);
+          ajaxSetJobOverrideProperty(project, ret, req, user);
         }
       } else {
         ret.put("error", "Cannot execute command " + ajaxName);
@@ -734,7 +733,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
   }
 
   private void ajaxSetJobOverrideProperty(Project project,
-      HashMap<String, Object> ret, HttpServletRequest req)
+      HashMap<String, Object> ret, HttpServletRequest req, User user)
       throws ServletException {
     String flowName = getParam(req, "flowName");
     String jobName = getParam(req, "jobName");
@@ -756,7 +755,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
     @SuppressWarnings("unchecked")
     Props overrideParams = new Props(null, jobParamGroup);
     try {
-      projectManager.setJobOverrideProperty(project, overrideParams, jobName);
+      projectManager.setJobOverrideProperty(project, overrideParams, jobName, user);
     } catch (ProjectManagerException e) {
       ret.put("error", "Failed to upload job override property");
     }
diff --git a/azkaban-web-server/src/web/js/azkaban/view/project-logs.js b/azkaban-web-server/src/web/js/azkaban/view/project-logs.js
index f4bd5c3..178b678 100644
--- a/azkaban-web-server/src/web/js/azkaban/view/project-logs.js
+++ b/azkaban-web-server/src/web/js/azkaban/view/project-logs.js
@@ -29,7 +29,8 @@ var typeMapping = {
   "GROUP_PERMISSION" : "Group Permission",
   "DESCRIPTION" : "Description Set",
   "SCHEDULE": "Schedule",
-  "UPLOADED": "Uploaded"
+  "UPLOADED": "Uploaded",
+  "PROPERTY_OVERRIDE": "Property Override"
 };
 
 var projectLogView;
@@ -88,13 +89,20 @@ azkaban.ProjectLogView = Backbone.View.extend({
         $(containerUser).text(user);
 
         var containerType = document.createElement("td");
+        var containerMessage = document.createElement("td");
+
+        // If the event is a property override change, highlight by red color.
+        if(type == "PROPERTY_OVERRIDE"){
+          $(containerMessage).css("color", "red");
+          $(containerMessage).css("white-space", "pre-wrap");
+          $(containerType).css("color", "red");
+        }
+
         $(containerType).addClass("type");
         $(containerType).addClass(type);
         $(containerType).text(typeMapping[type] ? typeMapping[type] : type);
 
-        var containerMessage = document.createElement("td");
-        $(containerMessage).addClass("message");
-        $(containerMessage).text(message);
+        $(containerMessage).html(message);
 
         $(containerEvent).append(containerTime);
         $(containerEvent).append(containerUser);