001 package org.hackystat.tickertape.ticker; 002 003 import java.util.Date; 004 import java.util.logging.Logger; 005 006 import org.hackystat.sensorbase.client.SensorBaseClient; 007 import org.hackystat.tickertape.notifier.twitter.TwitterNotifier; 008 import org.hackystat.tickertape.tickerlingua.HackystatUser; 009 import org.hackystat.tickertape.tickerlingua.NotificationService; 010 import org.hackystat.tickertape.tickerlingua.TickerLingua; 011 import org.hackystat.tickertape.tickerlingua.Tickertape; 012 import org.hackystat.tickertape.tickerlingua.HackystatProject; 013 import org.hackystat.tickertape.ticker.data.MultiProjectSensorDataLog; 014 import org.hackystat.tickertape.ticker.data.ProjectSensorDataLog; 015 016 /** 017 * Provides a Ticker that can monitor multiple Hackystat projects and send tweets to a single 018 * Twitter account occasionally to summarize activity. There is one tweet generated per user 019 * and per project. 020 * @author Philip Johnson 021 */ 022 public class MultiProjectTweetsTicker implements Ticker { 023 024 private TwitterNotifier twitterNotifier; 025 private Tickertape tickertape; 026 private Logger logger; 027 private TickerLingua tickerLingua; 028 private MultiProjectSensorDataLog multiLog = new MultiProjectSensorDataLog(); 029 030 /** The string used as the key for the TimeBetweenTweets property. */ 031 public static final String TIME_BETWEEN_TWEETS_KEY = "TimeBetweenTweets"; 032 033 034 /** 035 * The run method for this ticker. 036 * @param tickertape The Tickertape instance indicating what to do. 037 * @param tickerLingua The TickerLingua instance with global data info. 038 * @param logger The logger to be used to communicate status. 039 */ 040 public void run(Tickertape tickertape, TickerLingua tickerLingua, Logger logger) { 041 logger.info("Running MultiProjectTweetsTicker..."); 042 this.tickertape = tickertape; 043 this.logger = logger; 044 this.tickerLingua = tickerLingua; 045 notify(null); // Force a tweet just the first time we run this Ticker. 046 047 // Process each project. 048 for (HackystatProject project : tickertape.getHackystatProjects()) { 049 this.logger.info("\n\nChecking status of project " + project.getName()); 050 051 // Find or create the ProjectSensorDataLog. 052 String projectName = project.getName(); 053 String projectOwner = project.getHackystatOwner().getHackystatUserAccount(); 054 HackystatUser authUser = project.getHackystatAuthUser(); 055 SensorBaseClient client = authUser.getSensorBaseClient(); 056 ProjectSensorDataLog log = multiLog.get(client, this.getMaxLife(), projectOwner, projectName, 057 logger); 058 059 // Now get any new data. 060 log.update(); 061 062 for (String user : log.getProjectParticipants()) { 063 if (log.hasRecentSensorData(user) && !log.hasRecentTweet(user)) { 064 String workedOnMsg = this.getWorkedOnMsg(log, user); 065 String builtMsg = this.getBuiltMsg(log, user); 066 String testMsg = this.getTestMsg(log, user); 067 String commitMsg = this.getCommitMsg(log, user); 068 String toolMsg = log.getToolString(user); 069 StringBuffer buff = new StringBuffer(); 070 buff.append(getShortName(user)).append(' '); 071 if (workedOnMsg != null) { 072 buff.append(workedOnMsg).append(", "); 073 } 074 if (builtMsg != null) { 075 buff.append(builtMsg).append(", "); 076 } 077 if (testMsg != null) { 078 buff.append(testMsg).append(", "); 079 } 080 if (commitMsg != null) { 081 buff.append(commitMsg).append(", "); 082 } 083 if (toolMsg != null) { 084 buff.append("using ").append(toolMsg).append(" for ").append(project.getShortName()); 085 } 086 notify(buff.toString()); 087 log.setTweet(user); 088 } 089 } 090 } 091 logger.info("Finished running MultiProjectTweetsTicker."); 092 } 093 094 /** 095 * Returns a string indicating the files worked on, or null if no files were worked on. 096 * @param log The ProjectSensorData log. 097 * @param user The user of interest. 098 * @return The string indicating work, or null. 099 */ 100 private String getWorkedOnMsg(ProjectSensorDataLog log, String user) { 101 String mostWorkedFile = log.mostWorkedOnFile(user); 102 return (mostWorkedFile == null) ? null : 103 String.format("worked on %s file(s) (including %s)", log.getNumFilesWorkedOn(user), 104 mostWorkedFile); 105 } 106 107 /** 108 * Returns a string indicating build activity. 109 * @param log The ProjectSensorDataLog. 110 * @param user The user. 111 * @return A string indicating build activity, or null if there was none. 112 */ 113 private String getBuiltMsg(ProjectSensorDataLog log, String user) { 114 int numBuilds = log.getSensorDataCount(user, "Build"); 115 return (numBuilds == 0) ? null : 116 String.format("built %s time(s) (%s successful)", numBuilds, 117 log.getBuildSuccessCount(user)); 118 } 119 120 121 /** 122 * Returns a string indicating commit activity. 123 * @param log The ProjectSensorDataLog. 124 * @param user The user. 125 * @return A string indicating commit activity, or null if there was none. 126 */ 127 private String getCommitMsg(ProjectSensorDataLog log, String user) { 128 int numCommits = log.getSensorDataCount(user, "Commit"); 129 return (numCommits == 0) ? null : 130 String.format("committed %s file(s)", numCommits); 131 } 132 133 /** 134 * Returns a string indicating build activity. 135 * @param log The ProjectSensorDataLog. 136 * @param user The user. 137 * @return A string indicating build activity, or null if there was none. 138 */ 139 private String getTestMsg(ProjectSensorDataLog log, String user) { 140 int numTests = log.getSensorDataCount(user, "UnitTest"); 141 return (numTests == 0) ? null : 142 String.format("ran %s test(s) (%s passing)", numTests, log.getTestPassCount(user)); 143 } 144 145 146 /** 147 * Returns the shortname associated with this email, or the email if the shortname could not be 148 * found. 149 * @param email The email corresponding to a Hackystat user account. 150 * @return The short name or the email. 151 */ 152 private String getShortName(String email) { 153 for (HackystatUser user : this.tickerLingua.getHackystatUsers()) { 154 if (email.equals(user.getHackystatUserAccount())) { 155 return user.getShortName(); 156 } 157 } 158 return email; 159 } 160 161 162 /** 163 * Sends a message to the Twitter account associated with this ticker, if the message is not null. 164 * @param message The message to be sent. 165 */ 166 private void notify(String message) { 167 if (this.twitterNotifier == null) { 168 NotificationService notifier = tickertape.getNotificationServices().get(0); 169 this.twitterNotifier = new TwitterNotifier(notifier.getId(), notifier.getPassword(), logger); 170 this.twitterNotifier.notify("Starting up a new TwitterNotifier at: " + new Date()); 171 } 172 if (message != null) { 173 this.twitterNotifier.notify(message); 174 } 175 } 176 177 /** 178 * Gets the maxLife by looking for the TimeBetweenTweets ticker property and converting it to 179 * a double. Returns 0.5 (30 minutes) if the property is not found or could not be parsed. 180 * @return The maxLife. 181 */ 182 private double getMaxLife() { 183 double defaultMaxLife = 0.5; 184 String timeBetweenTweets = this.tickertape.getTickerProperties().get(TIME_BETWEEN_TWEETS_KEY); 185 if (timeBetweenTweets == null) { 186 return defaultMaxLife; 187 } 188 try { 189 return Double.parseDouble(timeBetweenTweets); 190 } 191 catch (Exception e) { 192 this.logger.warning("Error parsing TimeBetweenTweets: " + timeBetweenTweets); 193 return defaultMaxLife; 194 } 195 } 196 }