azkaban-uncached

data trigger with ui

7/7/2013 6:59:58 PM

Details

diff --git a/src/java/azkaban/scheduler/BasicTimeChecker.java b/src/java/azkaban/scheduler/BasicTimeChecker.java
index 9d35746..1bdb747 100644
--- a/src/java/azkaban/scheduler/BasicTimeChecker.java
+++ b/src/java/azkaban/scheduler/BasicTimeChecker.java
@@ -242,5 +242,10 @@ public class BasicTimeChecker implements ConditionChecker {
 		return jsonObj;
 	}
 
+	@Override
+	public void stopChecker() {
+		return;
+	}
+
 
 }
diff --git a/src/java/azkaban/scheduler/ScheduleManager.java b/src/java/azkaban/scheduler/ScheduleManager.java
index 06cdb2c..1db4b10 100644
--- a/src/java/azkaban/scheduler/ScheduleManager.java
+++ b/src/java/azkaban/scheduler/ScheduleManager.java
@@ -47,7 +47,9 @@ import azkaban.sla.SLA.SlaRule;
 import azkaban.sla.SLA.SlaSetting;
 import azkaban.sla.SLAManager;
 import azkaban.sla.SlaOptions;
-import azkaban.trigger.TriggerServicer;
+import azkaban.trigger.Trigger;
+import azkaban.trigger.TriggerAgent;
+import azkaban.trigger.TriggerStatus;
 import azkaban.utils.Pair;
 import azkaban.utils.Props;
 
@@ -57,7 +59,7 @@ import azkaban.utils.Props;
  * the flow from the schedule when it is run, which can potentially allow the
  * flow to and overlap each other.
  */
-public class ScheduleManager implements TriggerServicer {
+public class ScheduleManager implements TriggerAgent {
 	private static Logger logger = Logger.getLogger(ScheduleManager.class);
 
 	public static final String triggerSource = "SimpleTimeTrigger";
@@ -141,17 +143,17 @@ public class ScheduleManager implements TriggerServicer {
 	 * @throws ScheduleManagerException 
 	 */
 	public synchronized List<Schedule> getSchedules() {
-		if(useExternalRunner) {
-			for(Schedule s : scheduleIDMap.values()) {
-				try {
-					loader.updateNextExecTime(s);
-				} catch (ScheduleManagerException e) {
-					// TODO Auto-generated catch block
-					e.printStackTrace();
-					logger.error("Failed to update schedule from external runner for schedule " + s.getScheduleId());
-				}
-			}
-		}
+//		if(useExternalRunner) {
+//			for(Schedule s : scheduleIDMap.values()) {
+//				try {
+//					loader.updateNextExecTime(s);
+//				} catch (ScheduleManagerException e) {
+//					// TODO Auto-generated catch block
+//					e.printStackTrace();
+//					logger.error("Failed to update schedule from external runner for schedule " + s.getScheduleId());
+//				}
+//			}
+//		}
 		
 		//return runner.getRunnerSchedules();
 		return new ArrayList<Schedule>(scheduleIDMap.values());
@@ -336,7 +338,7 @@ public class ScheduleManager implements TriggerServicer {
 	}
 	
 	@Override
-	public void createTriggerFromProps(Props props) throws ScheduleManagerException {
+	public void loadTriggerFromProps(Props props) throws ScheduleManagerException {
 		throw new ScheduleManagerException("create " + getTriggerSource() + " from json not supported yet" );
 		
 	}
@@ -345,6 +347,21 @@ public class ScheduleManager implements TriggerServicer {
 	public String getTriggerSource() {
 		return triggerSource;
 	}
+	
+	@Override
+	public void updateLocal(Trigger t) {
+		if(t.getStatus().equals(TriggerStatus.EXPIRED)) {
+			removeSchedule(getSchedule(t.getTriggerId()));
+		} else {
+			try {
+				loader.updateNextExecTime(getSchedule(t.getTriggerId()));
+			} catch (ScheduleManagerException e) {
+				// TODO Auto-generated catch block
+				e.printStackTrace();
+				logger.error("Failed to get updated next execution time on schedule " + getSchedule(t.getTriggerId()).toString());
+			}
+		}
+	}
 
 	/**
 	 * Thread that simply invokes the running of flows when the schedule is
@@ -618,4 +635,8 @@ public class ScheduleManager implements TriggerServicer {
 		}
 	}
 
+	
+
+	
+
 }
diff --git a/src/java/azkaban/scheduler/TriggerBasedScheduleLoader.java b/src/java/azkaban/scheduler/TriggerBasedScheduleLoader.java
index 7a7200a..b7e0478 100644
--- a/src/java/azkaban/scheduler/TriggerBasedScheduleLoader.java
+++ b/src/java/azkaban/scheduler/TriggerBasedScheduleLoader.java
@@ -102,7 +102,7 @@ public class TriggerBasedScheduleLoader implements ScheduleLoader {
 	//TODO
 	// may need to add logic to filter out skip runs
 	@Override
-	public List<Schedule> loadSchedules() throws ScheduleManagerException {
+	public synchronized List<Schedule> loadSchedules() throws ScheduleManagerException {
 		List<Trigger> triggers = triggerManager.getTriggers();
 		List<Schedule> schedules = new ArrayList<Schedule>();
 		triggersLocalCopy = new HashMap<Integer, Trigger>();
diff --git a/src/java/azkaban/trigger/ConditionChecker.java b/src/java/azkaban/trigger/ConditionChecker.java
index 14ca333..8ce3c46 100644
--- a/src/java/azkaban/trigger/ConditionChecker.java
+++ b/src/java/azkaban/trigger/ConditionChecker.java
@@ -17,4 +17,6 @@ public interface ConditionChecker {
 	
 	Object toJson();
 
+	void stopChecker();
+
 }
diff --git a/src/java/azkaban/trigger/Trigger.java b/src/java/azkaban/trigger/Trigger.java
index 56b7d80..673facc 100644
--- a/src/java/azkaban/trigger/Trigger.java
+++ b/src/java/azkaban/trigger/Trigger.java
@@ -16,8 +16,8 @@ public class Trigger {
 	private DateTime lastModifyTime;
 	private DateTime submitTime;
 	private String submitUser;
-	
 	private String source;
+	private TriggerStatus status = TriggerStatus.READY;
 	
 	private Condition triggerCondition;
 	private Condition expireCondition;
@@ -41,6 +41,14 @@ public class Trigger {
 		return submitUser;
 	}
 
+	public TriggerStatus getStatus() {
+		return status;
+	}
+
+	public void setStatus(TriggerStatus status) {
+		this.status = status;
+	}
+
 	public Condition getTriggerCondition() {
 		return triggerCondition;
 	}
@@ -164,6 +172,7 @@ public class Trigger {
 		jsonObj.put("submitTime", String.valueOf(submitTime.getMillis()));
 		jsonObj.put("lastModifyTime", String.valueOf(lastModifyTime.getMillis()));
 		jsonObj.put("triggerId", String.valueOf(triggerId));
+		jsonObj.put("status", status.toString());
 		
 		return jsonObj;
 	}
@@ -203,9 +212,11 @@ public class Trigger {
 			DateTime submitTime = new DateTime(submitTimeMillis);
 			DateTime lastModifyTime = new DateTime(lastModifyTimeMillis);
 			int triggerId = Integer.valueOf((String) jsonObj.get("triggerId"));
+			TriggerStatus status = TriggerStatus.valueOf((String)jsonObj.get("status"));
 			trigger = new Trigger(triggerId, lastModifyTime, submitTime, submitUser, source, triggerCond, expireCond, actions);
 			trigger.setResetOnExpire(resetOnExpire);
 			trigger.setResetOnTrigger(resetOnTrigger);
+			trigger.setStatus(status);
 		}catch(Exception e) {
 			e.printStackTrace();
 			logger.error("Failed to decode the trigger.", e);
@@ -226,5 +237,16 @@ public class Trigger {
 				" and expire condition of " + expireCondition.getExpression() + 
 				actionsString;
 	}
+
+	public void stopCheckers() {
+		for(ConditionChecker checker : triggerCondition.getCheckers().values()) {
+			checker.stopChecker();
+		}
+		for(ConditionChecker checker : expireCondition.getCheckers().values()) {
+			checker.stopChecker();
+		}
+		
+	}
+
 	
 }
diff --git a/src/java/azkaban/trigger/TriggerManager.java b/src/java/azkaban/trigger/TriggerManager.java
index 09bbae1..1395340 100644
--- a/src/java/azkaban/trigger/TriggerManager.java
+++ b/src/java/azkaban/trigger/TriggerManager.java
@@ -29,7 +29,7 @@ public class TriggerManager {
 	
 	private static TriggerScannerThread scannerThread;
 	
-	private Map<String, TriggerServicer> triggerServicers = new HashMap<String, TriggerServicer>();
+	private Map<String, TriggerAgent> triggerAgents = new HashMap<String, TriggerAgent>();
 	
 	public TriggerManager(Props props, TriggerLoader triggerLoader) {
 		
@@ -80,20 +80,20 @@ public class TriggerManager {
 		for(File triggerFile : triggerFiles) {
 			Props triggerProps = new Props(props, triggerFile);
 			String triggerType = triggerProps.getString("trigger.type");
-			TriggerServicer servicer = triggerServicers.get(triggerType);
-			if(servicer != null) {
-				servicer.createTriggerFromProps(triggerProps);
+			TriggerAgent agent = triggerAgents.get(triggerType);
+			if(agent != null) {
+				agent.loadTriggerFromProps(triggerProps);
 			} else {
 				throw new Exception("Trigger " + triggerType + " is not supported.");
 			}
 		}
 	}
 	
-	public void addTriggerServicer(String triggerSource, TriggerServicer triggerServicer) throws TriggerManagerException {
-		if(triggerServicers.containsKey(triggerSource)) {
-			throw new TriggerManagerException("Trigger Servicer " + triggerSource + " already exists!" );
+	public void addTriggerAgent(String triggerSource, TriggerAgent agent) throws TriggerManagerException {
+		if(triggerAgents.containsKey(triggerSource)) {
+			throw new TriggerManagerException("Trigger agent " + triggerSource + " already exists!" );
 		}
-		this.triggerServicers.put(triggerSource, triggerServicer);
+		this.triggerAgents.put(triggerSource, agent);
 	}
 	
 	public void start() {
@@ -110,8 +110,8 @@ public class TriggerManager {
 			logger.error(e.getMessage());
 		}
 		
-		for(TriggerServicer servicer : triggerServicers.values()) {
-			servicer.load();
+		for(TriggerAgent agent : triggerAgents.values()) {
+			agent.load();
 		}
 		
 		scannerThread.start();
@@ -133,10 +133,13 @@ public class TriggerManager {
 	}
 	
 	public synchronized void removeTrigger(int id) throws TriggerManagerException {
-		removeTrigger(triggerIdMap.get(id));
+		Trigger t = triggerIdMap.get(id);
+		if(t != null) {
+			removeTrigger(triggerIdMap.get(id));
+		}
 	}
 	
-	//TODO: update corresponding servicers
+	//TODO: update corresponding agents
 	public synchronized void updateTrigger(Trigger t) throws TriggerManagerException {
 		if(!triggerIdMap.containsKey(t.getTriggerId())) {
 			throw new TriggerManagerException("The trigger to update doesn't exist!");
@@ -146,12 +149,12 @@ public class TriggerManager {
 		scannerThread.addTrigger(t);
 		triggerIdMap.put(t.getTriggerId(), t);
 		
-		
 		triggerLoader.updateTrigger(t);
 	}
-	
-	//TODO: update corresponding servicers
+
+	//TODO: update corresponding agents
 	public synchronized void removeTrigger(Trigger t) throws TriggerManagerException {
+		t.stopCheckers();
 		triggerLoader.removeTrigger(t);
 		scannerThread.deleteTrigger(t);
 		triggerIdMap.remove(t.getTriggerId());		
@@ -165,7 +168,13 @@ public class TriggerManager {
 		return checkerLoader.getSupportedCheckers();
 	}
 
-	
+	private void updateAgent(Trigger t) {
+		TriggerAgent agent = triggerAgents.get(t.getSource());
+		if(agent != null) {
+			agent.updateLocal(t);
+		}
+		
+	}
 	
 	//trigger scanner thread
 	public class TriggerScannerThread extends Thread {
@@ -240,10 +249,12 @@ public class TriggerManager {
 		
 		private void checkAllTriggers() throws TriggerManagerException {
 			for(Trigger t : triggers) {
-				if(t.triggerConditionMet()) {
-					onTriggerTrigger(t);
-				} else if (t.expireConditionMet()) {
-					onTriggerExpire(t);
+				if(t.getStatus().equals(TriggerStatus.READY)) {
+					if(t.triggerConditionMet()) {
+						onTriggerTrigger(t);
+					} else if (t.expireConditionMet()) {
+						onTriggerExpire(t);
+					}
 				}
 			}
 		}
@@ -263,8 +274,9 @@ public class TriggerManager {
 				t.resetExpireCondition();
 				updateTrigger(t);
 			} else {
-				removeTrigger(t);
+				t.setStatus(TriggerStatus.EXPIRED);
 			}
+			updateAgent(t);
 		}
 		
 		private void onTriggerExpire(Trigger t) throws TriggerManagerException {
@@ -273,8 +285,9 @@ public class TriggerManager {
 				t.resetExpireCondition();
 				updateTrigger(t);
 			} else {
-				removeTrigger(t);
+				t.setStatus(TriggerStatus.EXPIRED);
 			}
+			updateAgent(t);
 		}
 	}
 
@@ -282,4 +295,10 @@ public class TriggerManager {
 		return triggerIdMap.get(triggerId);
 	}
 
+	public void expireTrigger(int triggerId) {
+		Trigger t = getTrigger(triggerId);
+		t.setStatus(TriggerStatus.EXPIRED);
+		updateAgent(t);
+	}
+
 }
diff --git a/src/java/azkaban/trigger/TriggerStatus.java b/src/java/azkaban/trigger/TriggerStatus.java
new file mode 100644
index 0000000..3fcadf7
--- /dev/null
+++ b/src/java/azkaban/trigger/TriggerStatus.java
@@ -0,0 +1,29 @@
+package azkaban.trigger;
+
+public enum TriggerStatus {
+	READY(10), PAUSED(20), EXPIRED(30);
+	
+	private int numVal;
+
+	TriggerStatus(int numVal) {
+		this.numVal = numVal;
+	}
+
+	public int getNumVal() {
+		return numVal;
+	}
+	
+	public static TriggerStatus fromInteger(int x) {
+		switch (x) {
+		case 10:
+			return READY;
+		case 20:
+			return PAUSED;
+		case 30:
+			return EXPIRED;
+		default:
+			return READY;
+		}
+	}
+
+}
diff --git a/src/java/azkaban/webapp/AzkabanWebServer.java b/src/java/azkaban/webapp/AzkabanWebServer.java
index 61b7dcd..e92eb47 100644
--- a/src/java/azkaban/webapp/AzkabanWebServer.java
+++ b/src/java/azkaban/webapp/AzkabanWebServer.java
@@ -71,7 +71,7 @@ import azkaban.sla.SLAManagerException;
 import azkaban.trigger.JdbcTriggerLoader;
 import azkaban.trigger.TriggerLoader;
 import azkaban.trigger.TriggerManager;
-import azkaban.trigger.TriggerServicer;
+import azkaban.trigger.TriggerAgent;
 import azkaban.user.UserManager;
 import azkaban.user.XmlUserManager;
 import azkaban.utils.FileIOUtils;
@@ -499,11 +499,11 @@ public class AzkabanWebServer extends AzkabanServer {
 		Map<String, TriggerPlugin> triggerPlugins = loadTriggerPlugins(root, triggerPluginDir, app);
 		app.setTriggerPlugins(triggerPlugins);
 		// always have basic time trigger
-		app.getTriggerManager().addTriggerServicer(app.getScheduleManager().getTriggerSource(), app.getScheduleManager());
+		app.getTriggerManager().addTriggerAgent(app.getScheduleManager().getTriggerSource(), app.getScheduleManager());
 		// add additional triggers
 		for(TriggerPlugin plugin : triggerPlugins.values()) {
-			TriggerServicer servicer = plugin.getServicer();
-			app.getTriggerManager().addTriggerServicer(servicer.getTriggerSource(), servicer);
+			TriggerAgent agent = plugin.getAgent();
+			app.getTriggerManager().addTriggerAgent(agent.getTriggerSource(), agent);
 		}
 		// fire up
 		app.getTriggerManager().start();
@@ -582,7 +582,7 @@ public class AzkabanWebServer extends AzkabanServer {
 			}
 			
 			String pluginName = pluginProps.getString("trigger.name");
-			String pluginWebPath = pluginProps.getString("trigger.path");
+			String pluginWebPath = pluginProps.getString("trigger.web.path");
 			int pluginOrder = pluginProps.getInt("trigger.order", 0);
 			boolean pluginHidden = pluginProps.getBoolean("trigger.hidden", false);
 			List<String> extLibClasspath = pluginProps.getStringList("trigger.external.classpaths", (List<String>)null);
diff --git a/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java b/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
index 8e87919..0a8415a 100644
--- a/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
+++ b/src/java/azkaban/webapp/servlet/AbstractAzkabanServlet.java
@@ -63,7 +63,9 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
 	private String color;
 
 	private List<ViewerPlugin> viewerPlugins;
-
+	private List<TriggerPlugin> triggerPlugins;
+	
+	
 	/**
 	 * To retrieve the application for the servlet
 	 * 
@@ -90,6 +92,7 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
 		if (application instanceof AzkabanWebServer) {
 			AzkabanWebServer server = (AzkabanWebServer)application;
 			viewerPlugins = server.getViewerPlugins();
+			triggerPlugins = new ArrayList<TriggerPlugin>(server.getTriggerPlugins().values());
 		}
 	}
 	
@@ -302,6 +305,10 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
 //			page.add("viewerPath", plugin.getPluginPath());
 		}
 		
+		if(triggerPlugins != null && !triggerPlugins.isEmpty()) {
+			page.add("triggers", triggerPlugins);
+		}
+		
 		return page;
 	}
 
@@ -330,6 +337,10 @@ public abstract class AbstractAzkabanServlet extends HttpServlet {
 			page.add("viewerPath", plugin.getPluginPath());
 		}
 		
+		if(triggerPlugins != null && !triggerPlugins.isEmpty()) {
+			page.add("triggers", triggerPlugins);
+		}
+		
 		return page;
 	}
 	
diff --git a/src/java/azkaban/webapp/servlet/TriggerManagerServlet.java b/src/java/azkaban/webapp/servlet/TriggerManagerServlet.java
index 73d11d7..44f0f1a 100644
--- a/src/java/azkaban/webapp/servlet/TriggerManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/TriggerManagerServlet.java
@@ -61,8 +61,8 @@ public class TriggerManagerServlet extends LoginAbstractAzkabanServlet {
 		String ajaxName = getParam(req, "ajax");
 		
 		try {
-			if (ajaxName.equals("removeTrigger")) {
-				ajaxRemoveTrigger(req, ret, session.getUser());
+			if (ajaxName.equals("expireTrigger")) {
+				ajaxExpireTrigger(req, ret, session.getUser());
 			}
 		} catch (Exception e) {
 			ret.put("error", e.getMessage());
@@ -94,7 +94,7 @@ public class TriggerManagerServlet extends LoginAbstractAzkabanServlet {
 		}
 	}
 
-	private void ajaxRemoveTrigger(HttpServletRequest req, Map<String, Object> ret, User user) throws ServletException, TriggerManagerException{
+	private void ajaxExpireTrigger(HttpServletRequest req, Map<String, Object> ret, User user) throws ServletException, TriggerManagerException{
 		int triggerId = getIntParam(req, "triggerId");
 		Trigger t = triggerManager.getTrigger(triggerId);
 		if(t == null) {
@@ -109,7 +109,7 @@ public class TriggerManagerServlet extends LoginAbstractAzkabanServlet {
 //			return;
 //		}
 
-		triggerManager.removeTrigger(triggerId);
+		triggerManager.expireTrigger(triggerId);
 		logger.info("User '" + user.getUserId() + " has removed trigger " + t.getDescription());
 //		projectManager.postProjectEvent(project, EventType.SCHEDULE, user.getUserId(), "Schedule " + sched.toString() + " has been removed.");
 		
diff --git a/src/java/azkaban/webapp/servlet/TriggerPlugin.java b/src/java/azkaban/webapp/servlet/TriggerPlugin.java
index ee5e620..b2d9db9 100644
--- a/src/java/azkaban/webapp/servlet/TriggerPlugin.java
+++ b/src/java/azkaban/webapp/servlet/TriggerPlugin.java
@@ -1,6 +1,6 @@
 package azkaban.webapp.servlet;
 
-import azkaban.trigger.TriggerServicer;
+import azkaban.trigger.TriggerAgent;
 
 public interface TriggerPlugin {
 	
@@ -13,6 +13,21 @@ public interface TriggerPlugin {
 //	}
 
 	public AbstractAzkabanServlet getServlet();
-	public TriggerServicer getServicer();
+	public TriggerAgent getAgent();
 	public void load();
+	
+	public String getPluginName();
+
+	public String getPluginPath();
+
+	public int getOrder();
+	
+	public boolean isHidden();
+
+	public void setHidden(boolean hidden);
+	
+	public String getInputPanelVM();
+	
+	
+	
 }
diff --git a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
index 6d4ddcb..14f2b42 100644
--- a/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/flowexecutionpanel.vm
@@ -135,6 +135,12 @@
 		#if(!$show_schedule || $show_schedule == 'true') 
 		<a class="btn2" id="schedule-btn">Schedule</a>
 		#end
+		
+		#if( $triggers.size() > 0 ) 
+			#foreach( $trigger in $triggers )
+			<a class="btn2" id=set-$trigger.pluginName>$trigger.pluginName</a>
+			#end
+		#end
 
 		<a class="yes btn1" id="execute-btn">Execute</a>
 		<a class="no simplemodal-close btn3 closeExecPanel">Cancel</a>
@@ -146,6 +152,15 @@
 #parse( "azkaban/webapp/servlet/velocity/schedulepanel.vm" )
 #end
 
+#if( $triggers.size() > 0 ) 
+	#foreach( $trigger in $triggers )
+		#set ($prefix = $trigger.pluginName )
+		#set ($webpath = $trigger.pluginPath )
+		#parse( $trigger.inputPanelVM )
+	#end
+#end
+
+
 <div id="contextMenu">
 	
 </div>
diff --git a/src/java/azkaban/webapp/servlet/velocity/nav.vm b/src/java/azkaban/webapp/servlet/velocity/nav.vm
index 8ce7d72..2789f31 100644
--- a/src/java/azkaban/webapp/servlet/velocity/nav.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/nav.vm
@@ -36,6 +36,15 @@
 					</li>
 					#end
 				#end
+				
+				#foreach($trigger in $triggers)
+					#if(!$trigger.hidden)
+					<li #if($current_page == $trigger.pluginName) class="selected"#end onClick="navMenuClick('$!context/$trigger.pluginPath')">
+						<a href="$!context/$trigger.pluginPath">$trigger.pluginName</a>
+					</li>
+					#end
+				#end
+				
 			</ul>
 			
 			<div id="user-id">
diff --git a/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm b/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm
index 77a12c2..bf62ca4 100644
--- a/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/triggerspage.vm
@@ -84,7 +84,7 @@
 							<td>${trigger.source}</td>
 							<td>${trigger.submitUser}</td>
 							<td>${trigger.getDescription()}</td>
-							<td><button id="removeTriggerBtn" hidden="true" onclick="removeTrigger(${trigger.triggerId})" >Remove Trigger</button></td>
+							<td><button id="expireTriggerBtn" onclick="expireTrigger(${trigger.triggerId})" >Expire Trigger</button></td>
 						</tr>
 #end
 #else
diff --git a/src/web/js/azkaban.triggers.view.js b/src/web/js/azkaban.triggers.view.js
index f507dc1..1bff2fd 100644
--- a/src/web/js/azkaban.triggers.view.js
+++ b/src/web/js/azkaban.triggers.view.js
@@ -1,5 +1,24 @@
 $.namespace('azkaban');
 
+function expireTrigger(triggerId) {
+	var triggerURL = contextURL + "/triggers"
+	var redirectURL = contextURL + "/triggers"
+	$.post(
+			triggerURL,
+			{"ajax":"expireTrigger", "triggerId":triggerId},
+			function(data) {
+				if (data.error) {
+//                 alert(data.error)
+					$('#errorMsg').text(data.error);
+				}
+				else {
+// 		 alert("Schedule "+schedId+" removed!")
+					window.location = redirectURL;
+				}
+			},
+			"json"
+	)
+}
 
 function removeSched(scheduleId) {
 	var scheduleURL = contextURL + "/schedule"
diff --git a/unit/java/azkaban/test/trigger/ThresholdChecker.java b/unit/java/azkaban/test/trigger/ThresholdChecker.java
index c26f854..801a134 100644
--- a/unit/java/azkaban/test/trigger/ThresholdChecker.java
+++ b/unit/java/azkaban/test/trigger/ThresholdChecker.java
@@ -81,5 +81,11 @@ public class ThresholdChecker implements ConditionChecker{
 		return null;
 	}
 
+	@Override
+	public void stopChecker() {
+		return;
+		
+	}
+
 
 }