PageRenderTime 26ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/com/atlassian/labs/jira/workflow/HipChatPostFunctionFactory.java

https://bitbucket.org/atlassian/hipchat-for-jira
Java | 358 lines | 294 code | 58 blank | 6 comment | 9 complexity | 28d9cbc6eb3a13ec9fda698f7503e177 MD5 | raw file
  1. package com.atlassian.labs.jira.workflow;
  2. import com.atlassian.fugue.Either;
  3. import com.atlassian.hipchat.plugins.api.client.ClientError;
  4. import com.atlassian.hipchat.plugins.api.client.HipChatClient;
  5. import com.atlassian.hipchat.plugins.api.client.Room;
  6. import com.atlassian.hipchat.plugins.api.config.HipChatConfigurationManager;
  7. import com.atlassian.jira.bc.issue.search.SearchService;
  8. import com.atlassian.jira.plugin.workflow.AbstractWorkflowPluginFactory;
  9. import com.atlassian.jira.plugin.workflow.WorkflowPluginFunctionFactory;
  10. import com.atlassian.jira.security.JiraAuthenticationContext;
  11. import com.google.common.base.Function;
  12. import com.google.common.base.Joiner;
  13. import com.google.common.base.Splitter;
  14. import com.google.common.base.Strings;
  15. import com.google.common.collect.ImmutableMap;
  16. import com.google.common.collect.Iterables;
  17. import com.google.common.collect.Lists;
  18. import com.google.common.collect.Maps;
  19. import com.opensymphony.workflow.loader.AbstractDescriptor;
  20. import com.opensymphony.workflow.loader.FunctionDescriptor;
  21. import java.util.Collection;
  22. import java.util.Collections;
  23. import java.util.List;
  24. import java.util.Map;
  25. import static com.google.common.collect.Lists.newArrayList;
  26. /*
  27. This is the factory class responsible for dealing with the UI for the post-function.
  28. This is typically where you put default values into the velocity context and where you store user input.
  29. */
  30. public class HipChatPostFunctionFactory extends AbstractWorkflowPluginFactory implements WorkflowPluginFunctionFactory {
  31. public static final String ROOMS_TO_NOTIFY_CSV_IDS_PARAM = "roomsToNotifyCsvIds";
  32. public static final String JQL_FILTER_PARAM = "jql";
  33. public static final String NOTIFY_CLIENTS_PARAM = "notifyClients";
  34. public static final String MESSAGE_FILTER_PARAM = "message";
  35. private final HipChatClient hipChatClient;
  36. private final HipChatConfigurationManager configurationManager;
  37. private final SearchService searchService;
  38. private final JiraAuthenticationContext authenticationContext;
  39. public HipChatPostFunctionFactory(HipChatClient hipChatClient, HipChatConfigurationManager configurationManager,
  40. SearchService searchService, JiraAuthenticationContext authenticationContext) {
  41. this.hipChatClient = hipChatClient;
  42. this.configurationManager = configurationManager;
  43. this.searchService = searchService;
  44. this.authenticationContext = authenticationContext;
  45. }
  46. @Override
  47. protected void getVelocityParamsForInput(Map<String, Object> velocityParams) {
  48. EditDto editDto;
  49. if (configurationManager.getApiToken().isDefined())
  50. {
  51. editDto = getRooms().fold(
  52. new Function<ClientError, EditDto>()
  53. {
  54. @Override
  55. public EditDto apply(ClientError error)
  56. {
  57. return new EditDto(true, true);
  58. }
  59. },
  60. new Function<List<Room>, EditDto>()
  61. {
  62. @Override
  63. public EditDto apply(List<Room> rooms)
  64. {
  65. return new EditDto(toRoomDtos(rooms));
  66. }
  67. }
  68. );
  69. }
  70. else
  71. {
  72. editDto = new EditDto(false, false);
  73. }
  74. velocityParams.put("dto", editDto);
  75. }
  76. private List<RoomDto> toRoomDtos(List<Room> rooms)
  77. {
  78. return Lists.transform(rooms, new Function<Room, RoomDto>()
  79. {
  80. @Override
  81. public RoomDto apply(Room from)
  82. {
  83. return new RoomDto(String.valueOf(from.getId()), from.getName());
  84. }
  85. });
  86. }
  87. @Override
  88. protected void getVelocityParamsForEdit(Map<String, Object> velocityParams, AbstractDescriptor descriptor) {
  89. final EditDto editDto;
  90. if (configurationManager.getApiToken().isDefined())
  91. {
  92. FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor;
  93. final String jql = Strings.nullToEmpty((String) functionDescriptor.getArgs().get(JQL_FILTER_PARAM));
  94. final boolean notifyClients = Boolean.parseBoolean((String) functionDescriptor.getArgs().get(NOTIFY_CLIENTS_PARAM));
  95. final Iterable<String> roomsToNotifyIds = Splitter.on(",").split(Strings.nullToEmpty((String) functionDescriptor.getArgs().get(ROOMS_TO_NOTIFY_CSV_IDS_PARAM)));
  96. final String message = Strings.nullToEmpty((String) functionDescriptor.getArgs().get(MESSAGE_FILTER_PARAM));
  97. editDto = getRooms().fold(
  98. new Function<ClientError, EditDto>()
  99. {
  100. @Override
  101. public EditDto apply(ClientError error)
  102. {
  103. return new EditDto(true, true);
  104. }
  105. },
  106. new Function<List<Room>, EditDto>()
  107. {
  108. @Override
  109. public EditDto apply(List<Room> rooms)
  110. {
  111. return new EditDto(newArrayList(roomsToNotifyIds), jql, notifyClients, toRoomDtos(rooms), isValidQuery(jql), message);
  112. }
  113. }
  114. );
  115. }
  116. else
  117. {
  118. editDto = new EditDto(false, false);
  119. }
  120. velocityParams.put("dto", editDto);
  121. }
  122. private Either<ClientError, List<Room>> getRooms()
  123. {
  124. return hipChatClient.rooms().list().claim();
  125. }
  126. @Override
  127. protected void getVelocityParamsForView(Map<String, Object> velocityParams, AbstractDescriptor descriptor) {
  128. final ViewDto viewDto;
  129. if (configurationManager.getApiToken().isDefined()) {
  130. FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor;
  131. String jql = Strings.nullToEmpty((String) functionDescriptor.getArgs().get(JQL_FILTER_PARAM));
  132. boolean notifyClients = Boolean.parseBoolean((String) functionDescriptor.getArgs().get(NOTIFY_CLIENTS_PARAM));
  133. final Either<ClientError,List<Room>> rooms = getRooms();
  134. if (rooms.isRight())
  135. {
  136. // seems to be counterintuitive to load the full list, but its actually more efficient than a request per room starting from 2 rooms
  137. final ImmutableMap<Long, Room> roomsById = Maps.uniqueIndex(rooms.right().get(), new Function<Room, Long>()
  138. {
  139. @Override
  140. public Long apply(Room from)
  141. {
  142. return from.getId();
  143. }
  144. });
  145. Iterable<String> roomsToNotifyIds = Splitter.on(",").omitEmptyStrings().split(Strings.nullToEmpty((String) functionDescriptor.getArgs().get(ROOMS_TO_NOTIFY_CSV_IDS_PARAM)));
  146. viewDto = new ViewDto(newArrayList(Iterables.transform(roomsToNotifyIds, new Function<String, RoomDto>()
  147. {
  148. @Override
  149. public RoomDto apply(String from)
  150. {
  151. Long roomId = Long.valueOf(from);
  152. return new RoomDto(from, roomsById.containsKey(roomId) ? roomsById.get(roomId).getName() : null);
  153. }
  154. })), jql, notifyClients, isValidQuery(jql));
  155. }
  156. else
  157. {
  158. viewDto = new ViewDto(true, true);
  159. }
  160. } else {
  161. viewDto = new ViewDto(false, false);
  162. }
  163. velocityParams.put("dto", viewDto);
  164. }
  165. public ImmutableMap<String, String> getDescriptorParams(Map<String, Object> formParams) {
  166. String[] roomsIds = (String[]) formParams.get("roomsToNotify");
  167. String roomsToNotifyCsvIds = roomsIds != null ? Joiner.on(",").join(roomsIds) : "";
  168. String jql = extractSingleParam(formParams, "jql");
  169. String message = extractSingleParam(formParams, "message");
  170. final String notifyClients;
  171. if ( formParams.containsKey(NOTIFY_CLIENTS_PARAM) ) {
  172. notifyClients = extractSingleParam(formParams, NOTIFY_CLIENTS_PARAM);
  173. } else {
  174. notifyClients = "false";
  175. }
  176. return ImmutableMap.of(ROOMS_TO_NOTIFY_CSV_IDS_PARAM, roomsToNotifyCsvIds, JQL_FILTER_PARAM, jql, NOTIFY_CLIENTS_PARAM, notifyClients, MESSAGE_FILTER_PARAM, message);
  177. }
  178. private boolean isValidQuery(String jql) {
  179. if (Strings.isNullOrEmpty(jql)) {
  180. return true;
  181. }
  182. // the current user (admin) probably won't be the user who will run the query, but it's sufficient to just test if query is valid
  183. SearchService.ParseResult parseResult = searchService.parseQuery(authenticationContext.getLoggedInUser(), jql);
  184. return parseResult.isValid();
  185. }
  186. public static class ViewDto {
  187. private final String jql;
  188. private final Collection<RoomDto> roomsToNotify;
  189. private final boolean notifyClients;
  190. private final boolean apiTokenConfigured;
  191. private final boolean responseError;
  192. private final boolean jqlValid;
  193. public ViewDto(Collection<RoomDto> roomsToNotify, String jql, boolean notifyClients, boolean jqlValid) {
  194. this.roomsToNotify = roomsToNotify;
  195. this.jql = jql;
  196. this.notifyClients = notifyClients;
  197. this.apiTokenConfigured = true;
  198. this.responseError = false;
  199. this.jqlValid = jqlValid;
  200. }
  201. public ViewDto(boolean apiTokenConfigured, boolean responseError) {
  202. this.roomsToNotify = Collections.emptyList();
  203. this.jql = null;
  204. this.notifyClients = false;
  205. this.apiTokenConfigured = apiTokenConfigured;
  206. this.responseError = responseError;
  207. this.jqlValid = false;
  208. }
  209. public Collection<RoomDto> getRoomsToNotify() {
  210. return roomsToNotify;
  211. }
  212. public String getJql() {
  213. return jql;
  214. }
  215. public boolean isNotifyClients() {
  216. return notifyClients;
  217. }
  218. public boolean isApiTokenConfigured() {
  219. return apiTokenConfigured;
  220. }
  221. public boolean isResponseError() {
  222. return responseError;
  223. }
  224. public boolean isJqlValid() {
  225. return jqlValid;
  226. }
  227. }
  228. public static class EditDto {
  229. private final Collection<String> roomsToNotifyIds;
  230. private final String jql;
  231. private final boolean notifyClients;
  232. private final Collection<RoomDto> rooms;
  233. private final boolean apiTokenConfigured;
  234. private final boolean responseError;
  235. private final boolean jqlValid;
  236. private final String message;
  237. public EditDto(Collection<String> roomsToNotifyIds, String jql, boolean notifyClients, Collection<RoomDto> rooms, boolean jqlValid, String message) {
  238. this.roomsToNotifyIds = roomsToNotifyIds;
  239. this.jql = jql;
  240. this.notifyClients = notifyClients;
  241. this.rooms = rooms;
  242. this.apiTokenConfigured = true;
  243. this.responseError = false;
  244. this.jqlValid = jqlValid;
  245. this.message = message;
  246. }
  247. public EditDto(boolean apiTokenConfigured, boolean responseError) {
  248. this.roomsToNotifyIds = Collections.emptyList();
  249. this.rooms = Collections.emptyList();
  250. this.jql = null;
  251. this.notifyClients = false;
  252. this.apiTokenConfigured = apiTokenConfigured;
  253. this.responseError = responseError;
  254. this.jqlValid = false;
  255. this.message = null;
  256. }
  257. public EditDto(Collection<RoomDto> rooms) {
  258. this(Collections.<String>emptyList(), null, false, rooms, true, null);
  259. }
  260. public Collection<String> getRoomsToNotifyIds() {
  261. return roomsToNotifyIds;
  262. }
  263. public String getJql() {
  264. return jql;
  265. }
  266. public boolean isNotifyClients() {
  267. return notifyClients;
  268. }
  269. public Collection<RoomDto> getRooms() {
  270. return rooms;
  271. }
  272. public boolean isApiTokenConfigured() {
  273. return apiTokenConfigured;
  274. }
  275. public boolean isResponseError() {
  276. return responseError;
  277. }
  278. public boolean isJqlValid() {
  279. return jqlValid;
  280. }
  281. public String getMessage()
  282. {
  283. return message;
  284. }
  285. }
  286. public static class RoomDto {
  287. private final String id;
  288. private final String name;
  289. public RoomDto(String id, String name) {
  290. this.id = id;
  291. this.name = name;
  292. }
  293. public String getId() {
  294. return id;
  295. }
  296. public String getName() {
  297. return name;
  298. }
  299. }
  300. }