PageRenderTime 5769ms CodeModel.GetById 26ms RepoModel.GetById 4ms app.codeStats 0ms

/core/src/main/java/jenkins/widgets/HistoryPageFilter.java

https://gitlab.com/github-cloud-corp/jenkins
Java | 367 lines | 232 code | 37 blank | 98 comment | 73 complexity | bb051c5f9eddecc2e74d7f904a19b5b0 MD5 | raw file
  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2013-2014, CloudBees, Inc.
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. package jenkins.widgets;
  25. import com.google.common.collect.Iterables;
  26. import com.google.common.collect.Iterators;
  27. import hudson.model.Job;
  28. import hudson.model.Queue;
  29. import hudson.model.Run;
  30. import hudson.widgets.HistoryWidget;
  31. import javax.annotation.Nonnull;
  32. import java.util.ArrayList;
  33. import java.util.Collections;
  34. import java.util.Comparator;
  35. import java.util.Iterator;
  36. import java.util.LinkedList;
  37. import java.util.List;
  38. /**
  39. * History page filter.
  40. *
  41. * @author <a href="mailto:tom.fennelly@gmail.com">tom.fennelly@gmail.com</a>
  42. */
  43. public class HistoryPageFilter<T> {
  44. private final int maxEntries;
  45. private Long newerThan;
  46. private Long olderThan;
  47. private String searchString;
  48. // Need to use different Lists for Queue.Items and Runs because
  49. // we need access to them separately in the jelly files for rendering.
  50. public final List<HistoryPageEntry<Queue.Item>> queueItems = new ArrayList<HistoryPageEntry<Queue.Item>>();
  51. public final List<HistoryPageEntry<Run>> runs = new ArrayList<HistoryPageEntry<Run>>();
  52. public boolean hasUpPage = false; // there are newer builds than on this page
  53. public boolean hasDownPage = false; // there are older builds than on this page
  54. public long nextBuildNumber;
  55. public HistoryWidget widget;
  56. public long newestOnPage = Long.MIN_VALUE; // see updateNewestOldest()
  57. public long oldestOnPage = Long.MAX_VALUE; // see updateNewestOldest()
  58. /**
  59. * Create a history page filter instance.
  60. *
  61. * @param maxEntries The max number of entries allowed for the page.
  62. */
  63. public HistoryPageFilter(int maxEntries) {
  64. this.maxEntries = maxEntries;
  65. }
  66. /**
  67. * Set the 'newerThan' queue ID.
  68. * @param newerThan Queue IDs newer/greater than this queue ID take precedence on this page.
  69. */
  70. public void setNewerThan(Long newerThan) {
  71. if (olderThan != null) {
  72. throw new UnsupportedOperationException("Cannot set 'newerThan'. 'olderThan' already set.");
  73. }
  74. this.newerThan = newerThan;
  75. }
  76. /**
  77. * Set the 'olderThan' queue ID.
  78. * @param olderThan Queue IDs older/less than this queue ID take precedence on this page.
  79. */
  80. public void setOlderThan(Long olderThan) {
  81. if (newerThan != null) {
  82. throw new UnsupportedOperationException("Cannot set 'olderThan'. 'newerThan' already set.");
  83. }
  84. this.olderThan = olderThan;
  85. }
  86. /**
  87. * Set the search string used to narrow the filtered set of builds.
  88. * @param searchString The search string.
  89. */
  90. public void setSearchString(@Nonnull String searchString) {
  91. this.searchString = searchString;
  92. }
  93. /**
  94. * Add build items to the History page.
  95. *
  96. * @param runItems The items to be added. Assumes the items are in descending queue ID order i.e. newest first.
  97. * @deprecated Replaced by add(Iterable&lt;T&gt;) as of version 2.15
  98. */
  99. @Deprecated
  100. public void add(@Nonnull List<T> runItems) {
  101. addInternal(runItems);
  102. }
  103. /**
  104. * Add build items to the History page.
  105. *
  106. * @param runItems The items to be added. Assumes the items are in descending queue ID order i.e. newest first.
  107. * @since TODO
  108. */
  109. public void add(@Nonnull Iterable<T> runItems) {
  110. addInternal(runItems);
  111. }
  112. /**
  113. * Add run items and queued items to the History page.
  114. *
  115. * @param runItems The items to be added. Assumes the items are in descending queue ID order i.e. newest first.
  116. * @param queueItems The queue items to be added. Queue items do not need to be sorted.
  117. * @since TODO
  118. */
  119. public void add(@Nonnull Iterable<T> runItems, @Nonnull List<Queue.Item> queueItems) {
  120. sort(queueItems);
  121. addInternal(Iterables.concat(queueItems, runItems));
  122. }
  123. /**
  124. * Add items to the History page, internal implementation.
  125. * @param items The items to be added.
  126. * @param <ItemT> The type of items should either be T or Queue.Item.
  127. */
  128. private <ItemT> void addInternal(@Nonnull Iterable<ItemT> items) {
  129. // Note that items can be a large lazily evaluated collection,
  130. // so this method is optimized to only iterate through it as much as needed.
  131. if (!items.iterator().hasNext()) {
  132. return;
  133. }
  134. nextBuildNumber = getNextBuildNumber(items.iterator().next());
  135. if (newerThan == null && olderThan == null) {
  136. // Just return the first page of entries (newest)
  137. Iterator<ItemT> iter = items.iterator();
  138. while (iter.hasNext()) {
  139. add(iter.next());
  140. if (isFull()) {
  141. break;
  142. }
  143. }
  144. hasDownPage = iter.hasNext();
  145. } else if (newerThan != null) {
  146. int toFillCount = getFillCount();
  147. if (toFillCount > 0) {
  148. // Walk through the items and keep track of the oldest
  149. // 'toFillCount' items until we reach an item older than
  150. // 'newerThan' or the end of the list.
  151. LinkedList<ItemT> itemsToAdd = new LinkedList<>();
  152. Iterator<ItemT> iter = items.iterator();
  153. while (iter.hasNext()) {
  154. ItemT item = iter.next();
  155. if (HistoryPageEntry.getEntryId(item) > newerThan) {
  156. itemsToAdd.addLast(item);
  157. // Discard an item off the front of the list if we have
  158. // to (which means we would be able to page up).
  159. if (itemsToAdd.size() > toFillCount) {
  160. itemsToAdd.removeFirst();
  161. hasUpPage = true;
  162. }
  163. } else {
  164. break;
  165. }
  166. }
  167. if (itemsToAdd.size() == 0) {
  168. // All builds are older than newerThan ?
  169. hasDownPage = true;
  170. } else {
  171. // If there's less than a full page of items newer than
  172. // 'newerThan', then it's ok to fill the page with older items.
  173. if (itemsToAdd.size() < toFillCount) {
  174. // We have to restart the iterator and skip the items that we added (because
  175. // we may have popped an extra item off the iterator that did not get added).
  176. Iterator<ItemT> skippedIter = items.iterator();
  177. Iterators.skip(skippedIter, itemsToAdd.size());
  178. for (int i = itemsToAdd.size(); i < toFillCount && skippedIter.hasNext(); i++) {
  179. ItemT item = skippedIter.next();
  180. itemsToAdd.addLast(item);
  181. }
  182. }
  183. hasDownPage = iter.hasNext();
  184. for (Object item : itemsToAdd) {
  185. add(item);
  186. }
  187. }
  188. }
  189. } else if (olderThan != null) {
  190. Iterator<ItemT> iter = items.iterator();
  191. while (iter.hasNext()) {
  192. Object item = iter.next();
  193. if (HistoryPageEntry.getEntryId(item) >= olderThan) {
  194. hasUpPage = true;
  195. } else {
  196. add(item);
  197. if (isFull()) {
  198. hasDownPage = iter.hasNext();
  199. break;
  200. }
  201. }
  202. }
  203. }
  204. }
  205. public int size() {
  206. return queueItems.size() + runs.size();
  207. }
  208. private void sort(List<? extends Object> items) {
  209. // Queue items can start building out of order with how they got added to the queue. Sorting them
  210. // before adding to the page. They'll still get displayed before the building items coz they end
  211. // up in a different list in HistoryPageFilter.
  212. Collections.sort(items, new Comparator<Object>() {
  213. @Override
  214. public int compare(Object o1, Object o2) {
  215. long o1QID = HistoryPageEntry.getEntryId(o1);
  216. long o2QID = HistoryPageEntry.getEntryId(o2);
  217. if (o1QID < o2QID) {
  218. return 1;
  219. } else if (o1QID == o2QID) {
  220. return 0;
  221. } else {
  222. return -1;
  223. }
  224. }
  225. });
  226. }
  227. private long getNextBuildNumber(@Nonnull Object entry) {
  228. if (entry instanceof Queue.Item) {
  229. Queue.Task task = ((Queue.Item) entry).task;
  230. if (task instanceof Job) {
  231. return ((Job) task).getNextBuildNumber();
  232. }
  233. } else if (entry instanceof Run) {
  234. return ((Run) entry).getParent().getNextBuildNumber();
  235. }
  236. // TODO maybe this should be an error?
  237. return HistoryPageEntry.getEntryId(entry) + 1;
  238. }
  239. private void addQueueItem(Queue.Item item) {
  240. HistoryPageEntry<Queue.Item> entry = new HistoryPageEntry<>(item);
  241. queueItems.add(entry);
  242. updateNewestOldest(entry.getEntryId());
  243. }
  244. private void addRun(Run run) {
  245. HistoryPageEntry<Run> entry = new HistoryPageEntry<>(run);
  246. // Assert that runs have been added in descending order
  247. if (runs.size() > 0) {
  248. if (entry.getEntryId() > runs.get(runs.size() - 1).getEntryId()) {
  249. throw new IllegalStateException("Runs were out of order");
  250. }
  251. }
  252. runs.add(entry);
  253. updateNewestOldest(entry.getEntryId());
  254. }
  255. private void updateNewestOldest(long entryId) {
  256. newestOnPage = Math.max(newestOnPage, entryId);
  257. oldestOnPage = Math.min(oldestOnPage, entryId);
  258. }
  259. private boolean add(Object entry) {
  260. // Purposely not calling isFull(). May need to add a greater number of entries
  261. // to the page initially, newerThan then cutting it back down to size using cutLeading()
  262. if (entry instanceof Queue.Item) {
  263. Queue.Item item = (Queue.Item) entry;
  264. if (searchString != null && !fitsSearchParams(item)) {
  265. return false;
  266. }
  267. addQueueItem(item);
  268. return true;
  269. } else if (entry instanceof Run) {
  270. Run run = (Run) entry;
  271. if (searchString != null && !fitsSearchParams(run)) {
  272. return false;
  273. }
  274. addRun(run);
  275. return true;
  276. }
  277. return false;
  278. }
  279. private boolean isFull() {
  280. return (size() >= maxEntries);
  281. }
  282. /**
  283. * Get the number of items required to fill the page.
  284. *
  285. * @return The number of items required to fill the page.
  286. */
  287. private int getFillCount() {
  288. return Math.max(0, (maxEntries - size()));
  289. }
  290. private boolean fitsSearchParams(@Nonnull Queue.Item item) {
  291. if (fitsSearchString(item.getDisplayName())) {
  292. return true;
  293. } else if (fitsSearchString(item.getId())) {
  294. return true;
  295. }
  296. // Non of the fuzzy matches "liked" the search term.
  297. return false;
  298. }
  299. private boolean fitsSearchParams(@Nonnull Run run) {
  300. if (searchString == null) {
  301. return true;
  302. }
  303. if (fitsSearchString(run.getDisplayName())) {
  304. return true;
  305. } else if (fitsSearchString(run.getDescription())) {
  306. return true;
  307. } else if (fitsSearchString(run.getNumber())) {
  308. return true;
  309. } else if (fitsSearchString(run.getQueueId())) {
  310. return true;
  311. } else if (fitsSearchString(run.getResult())) {
  312. return true;
  313. }
  314. // Non of the fuzzy matches "liked" the search term.
  315. return false;
  316. }
  317. private boolean fitsSearchString(Object data) {
  318. if (searchString == null) {
  319. return true;
  320. }
  321. if (data != null) {
  322. if (data instanceof Number) {
  323. return data.toString().equals(searchString);
  324. } else {
  325. return data.toString().toLowerCase().contains(searchString);
  326. }
  327. }
  328. return false;
  329. }
  330. }