Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

TicketsPage.java 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. /*
  2. * Copyright 2013 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.wicket.pages;
  17. import java.text.MessageFormat;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import java.util.Comparator;
  22. import java.util.List;
  23. import java.util.Set;
  24. import java.util.TreeSet;
  25. import org.apache.wicket.Component;
  26. import org.apache.wicket.PageParameters;
  27. import org.apache.wicket.behavior.SimpleAttributeModifier;
  28. import org.apache.wicket.markup.html.basic.Label;
  29. import org.apache.wicket.markup.html.link.BookmarkablePageLink;
  30. import org.apache.wicket.markup.html.panel.Fragment;
  31. import org.apache.wicket.markup.repeater.Item;
  32. import org.apache.wicket.markup.repeater.data.DataView;
  33. import org.apache.wicket.markup.repeater.data.ListDataProvider;
  34. import com.gitblit.Constants.AccessPermission;
  35. import com.gitblit.Keys;
  36. import com.gitblit.models.RegistrantAccessPermission;
  37. import com.gitblit.models.RepositoryModel;
  38. import com.gitblit.models.TicketModel;
  39. import com.gitblit.models.TicketModel.Status;
  40. import com.gitblit.models.UserModel;
  41. import com.gitblit.tickets.QueryBuilder;
  42. import com.gitblit.tickets.QueryResult;
  43. import com.gitblit.tickets.TicketIndexer.Lucene;
  44. import com.gitblit.tickets.TicketLabel;
  45. import com.gitblit.tickets.TicketMilestone;
  46. import com.gitblit.tickets.TicketResponsible;
  47. import com.gitblit.utils.ArrayUtils;
  48. import com.gitblit.utils.StringUtils;
  49. import com.gitblit.wicket.GitBlitWebSession;
  50. import com.gitblit.wicket.TicketsUI;
  51. import com.gitblit.wicket.TicketsUI.TicketQuery;
  52. import com.gitblit.wicket.TicketsUI.TicketSort;
  53. import com.gitblit.wicket.WicketUtils;
  54. import com.gitblit.wicket.panels.LinkPanel;
  55. import com.gitblit.wicket.panels.TicketListPanel;
  56. import com.gitblit.wicket.panels.TicketSearchForm;
  57. public class TicketsPage extends RepositoryPage {
  58. final TicketResponsible any;
  59. public TicketsPage(PageParameters params) {
  60. super(params);
  61. if (!app().tickets().isReady()) {
  62. // tickets prohibited
  63. setResponsePage(SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryName));
  64. } else if (!app().tickets().hasTickets(getRepositoryModel())) {
  65. // no tickets for this repository
  66. setResponsePage(NoTicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));
  67. } else {
  68. String id = WicketUtils.getObject(params);
  69. if (id != null) {
  70. // view the ticket with the TicketPage
  71. setResponsePage(TicketPage.class, params);
  72. }
  73. }
  74. // set stateless page preference
  75. setStatelessHint(true);
  76. any = new TicketResponsible(getString("gb.any"), "[* TO *]", null);
  77. UserModel user = GitBlitWebSession.get().getUser();
  78. boolean isAuthenticated = user != null && user.isAuthenticated;
  79. final String [] statiiParam = params.getStringArray(Lucene.status.name());
  80. final String assignedToParam = params.getString(Lucene.responsible.name(), null);
  81. final String milestoneParam = params.getString(Lucene.milestone.name(), null);
  82. final String queryParam = params.getString("q", null);
  83. final String searchParam = params.getString("s", null);
  84. final String sortBy = Lucene.fromString(params.getString("sort", Lucene.created.name())).name();
  85. final boolean desc = !"asc".equals(params.getString("direction", "desc"));
  86. // add search form
  87. add(new TicketSearchForm("ticketSearchForm", repositoryName, searchParam, getClass(), params));
  88. final String activeQuery;
  89. if (!StringUtils.isEmpty(searchParam)) {
  90. activeQuery = searchParam;
  91. } else if (StringUtils.isEmpty(queryParam)) {
  92. activeQuery = "";
  93. } else {
  94. activeQuery = queryParam;
  95. }
  96. // build Lucene query from defaults and request parameters
  97. QueryBuilder qb = new QueryBuilder(queryParam);
  98. if (!qb.containsField(Lucene.rid.name())) {
  99. // specify the repository
  100. qb.and(Lucene.rid.matches(getRepositoryModel().getRID()));
  101. }
  102. if (!qb.containsField(Lucene.responsible.name())) {
  103. // specify the responsible
  104. qb.and(Lucene.responsible.matches(assignedToParam));
  105. }
  106. if (!qb.containsField(Lucene.milestone.name())) {
  107. // specify the milestone
  108. qb.and(Lucene.milestone.matches(milestoneParam));
  109. }
  110. if (!qb.containsField(Lucene.status.name()) && !ArrayUtils.isEmpty(statiiParam)) {
  111. // specify the states
  112. boolean not = false;
  113. QueryBuilder q = new QueryBuilder();
  114. for (String state : statiiParam) {
  115. if (state.charAt(0) == '!') {
  116. not = true;
  117. q.and(Lucene.status.doesNotMatch(state.substring(1)));
  118. } else {
  119. q.or(Lucene.status.matches(state));
  120. }
  121. }
  122. if (not) {
  123. qb.and(q.toString());
  124. } else {
  125. qb.and(q.toSubquery().toString());
  126. }
  127. }
  128. final String luceneQuery = qb.build();
  129. // open milestones
  130. List<TicketMilestone> milestones = app().tickets().getMilestones(getRepositoryModel(), Status.Open);
  131. TicketMilestone currentMilestone = null;
  132. if (!StringUtils.isEmpty(milestoneParam)) {
  133. for (TicketMilestone tm : milestones) {
  134. if (tm.name.equals(milestoneParam)) {
  135. // get the milestone (queries the index)
  136. currentMilestone = app().tickets().getMilestone(getRepositoryModel(), milestoneParam);
  137. break;
  138. }
  139. }
  140. if (currentMilestone == null) {
  141. // milestone not found, create a temporary one
  142. currentMilestone = new TicketMilestone(milestoneParam);
  143. String q = QueryBuilder.q(Lucene.rid.matches(getRepositoryModel().getRID())).and(Lucene.milestone.matches(milestoneParam)).build();
  144. currentMilestone.tickets = app().tickets().queryFor(q, 1, 0, Lucene.number.name(), true);
  145. milestones.add(currentMilestone);
  146. }
  147. }
  148. Fragment milestonePanel;
  149. if (currentMilestone == null) {
  150. milestonePanel = new Fragment("milestonePanel", "noMilestoneFragment", this);
  151. add(milestonePanel);
  152. } else {
  153. milestonePanel = new Fragment("milestonePanel", "milestoneProgressFragment", this);
  154. milestonePanel.add(new Label("currentMilestone", currentMilestone.name));
  155. if (currentMilestone.due == null) {
  156. milestonePanel.add(new Label("currentDueDate", getString("gb.notSpecified")));
  157. } else {
  158. milestonePanel.add(WicketUtils.createDateLabel("currentDueDate", currentMilestone.due, GitBlitWebSession
  159. .get().getTimezone(), getTimeUtils(), false));
  160. }
  161. Label label = new Label("progress");
  162. WicketUtils.setCssStyle(label, "width:" + currentMilestone.getProgress() + "%;");
  163. milestonePanel.add(label);
  164. milestonePanel.add(new LinkPanel("openTickets", null,
  165. MessageFormat.format(getString("gb.nOpenTickets"), currentMilestone.getOpenTickets()),
  166. TicketsPage.class,
  167. queryParameters(null, currentMilestone.name, TicketsUI.openStatii, null, sortBy, desc, 1)));
  168. milestonePanel.add(new LinkPanel("closedTickets", null,
  169. MessageFormat.format(getString("gb.nClosedTickets"), currentMilestone.getClosedTickets()),
  170. TicketsPage.class,
  171. queryParameters(null, currentMilestone.name, TicketsUI.closedStatii, null, sortBy, desc, 1)));
  172. milestonePanel.add(new Label("totalTickets", MessageFormat.format(getString("gb.nTotalTickets"), currentMilestone.getTotalTickets())));
  173. add(milestonePanel);
  174. }
  175. Fragment milestoneDropdown = new Fragment("milestoneDropdown", "milestoneDropdownFragment", this);
  176. PageParameters resetMilestone = queryParameters(queryParam, null, statiiParam, assignedToParam, sortBy, desc, 1);
  177. milestoneDropdown.add(new BookmarkablePageLink<Void>("resetMilestone", TicketsPage.class, resetMilestone));
  178. ListDataProvider<TicketMilestone> milestonesDp = new ListDataProvider<TicketMilestone>(milestones);
  179. DataView<TicketMilestone> milestonesMenu = new DataView<TicketMilestone>("milestone", milestonesDp) {
  180. private static final long serialVersionUID = 1L;
  181. @Override
  182. public void populateItem(final Item<TicketMilestone> item) {
  183. final TicketMilestone tm = item.getModelObject();
  184. PageParameters params = queryParameters(queryParam, tm.name, statiiParam, assignedToParam, sortBy, desc, 1);
  185. item.add(new LinkPanel("milestoneLink", null, tm.name, TicketsPage.class, params).setRenderBodyOnly(true));
  186. }
  187. };
  188. milestoneDropdown.add(milestonesMenu);
  189. milestonePanel.add(milestoneDropdown);
  190. // search or query tickets
  191. int page = Math.max(1, WicketUtils.getPage(params));
  192. int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25);
  193. List<QueryResult> results;
  194. if (StringUtils.isEmpty(searchParam)) {
  195. results = app().tickets().queryFor(luceneQuery, page, pageSize, sortBy, desc);
  196. } else {
  197. results = app().tickets().searchFor(getRepositoryModel(), searchParam, page, pageSize);
  198. }
  199. int totalResults = results.size() == 0 ? 0 : results.get(0).totalResults;
  200. // standard queries
  201. add(new BookmarkablePageLink<Void>("changesQuery", TicketsPage.class,
  202. queryParameters(
  203. Lucene.type.matches(TicketModel.Type.Proposal.name()),
  204. milestoneParam,
  205. statiiParam,
  206. assignedToParam,
  207. sortBy,
  208. desc,
  209. 1)));
  210. add(new BookmarkablePageLink<Void>("bugsQuery", TicketsPage.class,
  211. queryParameters(
  212. Lucene.type.matches(TicketModel.Type.Bug.name()),
  213. milestoneParam,
  214. statiiParam,
  215. assignedToParam,
  216. sortBy,
  217. desc,
  218. 1)));
  219. add(new BookmarkablePageLink<Void>("enhancementsQuery", TicketsPage.class,
  220. queryParameters(
  221. Lucene.type.matches(TicketModel.Type.Enhancement.name()),
  222. milestoneParam,
  223. statiiParam,
  224. assignedToParam,
  225. sortBy,
  226. desc,
  227. 1)));
  228. add(new BookmarkablePageLink<Void>("tasksQuery", TicketsPage.class,
  229. queryParameters(
  230. Lucene.type.matches(TicketModel.Type.Task.name()),
  231. milestoneParam,
  232. statiiParam,
  233. assignedToParam,
  234. sortBy,
  235. desc,
  236. 1)));
  237. add(new BookmarkablePageLink<Void>("questionsQuery", TicketsPage.class,
  238. queryParameters(
  239. Lucene.type.matches(TicketModel.Type.Question.name()),
  240. milestoneParam,
  241. statiiParam,
  242. assignedToParam,
  243. sortBy,
  244. desc,
  245. 1)));
  246. add(new BookmarkablePageLink<Void>("maintenanceQuery", TicketsPage.class,
  247. queryParameters(
  248. Lucene.type.matches(TicketModel.Type.Maintenance.name()),
  249. milestoneParam,
  250. statiiParam,
  251. assignedToParam,
  252. sortBy,
  253. desc,
  254. 1)));
  255. add(new BookmarkablePageLink<Void>("resetQuery", TicketsPage.class,
  256. queryParameters(
  257. null,
  258. milestoneParam,
  259. TicketsUI.openStatii,
  260. null,
  261. null,
  262. true,
  263. 1)));
  264. if (isAuthenticated) {
  265. add(new Label("userDivider"));
  266. add(new BookmarkablePageLink<Void>("createdQuery", TicketsPage.class,
  267. queryParameters(
  268. Lucene.createdby.matches(user.username),
  269. milestoneParam,
  270. statiiParam,
  271. assignedToParam,
  272. sortBy,
  273. desc,
  274. 1)));
  275. add(new BookmarkablePageLink<Void>("watchedQuery", TicketsPage.class,
  276. queryParameters(
  277. Lucene.watchedby.matches(user.username),
  278. milestoneParam,
  279. statiiParam,
  280. assignedToParam,
  281. sortBy,
  282. desc,
  283. 1)));
  284. add(new BookmarkablePageLink<Void>("mentionsQuery", TicketsPage.class,
  285. queryParameters(
  286. Lucene.mentions.matches(user.username),
  287. milestoneParam,
  288. statiiParam,
  289. assignedToParam,
  290. sortBy,
  291. desc,
  292. 1)));
  293. } else {
  294. add(new Label("userDivider").setVisible(false));
  295. add(new Label("createdQuery").setVisible(false));
  296. add(new Label("watchedQuery").setVisible(false));
  297. add(new Label("mentionsQuery").setVisible(false));
  298. }
  299. Set<TicketQuery> dynamicQueries = new TreeSet<TicketQuery>();
  300. for (TicketLabel label : app().tickets().getLabels(getRepositoryModel())) {
  301. String q = QueryBuilder.q(Lucene.labels.matches(label.name)).build();
  302. dynamicQueries.add(new TicketQuery(label.name, q).color(label.color));
  303. }
  304. for (QueryResult ticket : results) {
  305. if (!StringUtils.isEmpty(ticket.topic)) {
  306. String q = QueryBuilder.q(Lucene.topic.matches(ticket.topic)).build();
  307. dynamicQueries.add(new TicketQuery(ticket.topic, q));
  308. }
  309. if (!ArrayUtils.isEmpty(ticket.labels)) {
  310. for (String label : ticket.labels) {
  311. String q = QueryBuilder.q(Lucene.labels.matches(label)).build();
  312. dynamicQueries.add(new TicketQuery(label, q));
  313. }
  314. }
  315. }
  316. if (dynamicQueries.size() == 0) {
  317. add(new Label("dynamicQueries").setVisible(false));
  318. } else {
  319. Fragment fragment = new Fragment("dynamicQueries", "dynamicQueriesFragment", this);
  320. ListDataProvider<TicketQuery> dynamicQueriesDp = new ListDataProvider<TicketQuery>(new ArrayList<TicketQuery>(dynamicQueries));
  321. DataView<TicketQuery> dynamicQueriesList = new DataView<TicketQuery>("dynamicQuery", dynamicQueriesDp) {
  322. private static final long serialVersionUID = 1L;
  323. @Override
  324. public void populateItem(final Item<TicketQuery> item) {
  325. final TicketQuery tq = item.getModelObject();
  326. Component swatch = new Label("swatch", "&nbsp;").setEscapeModelStrings(false);
  327. if (StringUtils.isEmpty(tq.color)) {
  328. // calculate a color
  329. tq.color = StringUtils.getColor(tq.name);
  330. }
  331. String background = MessageFormat.format("background-color:{0};", tq.color);
  332. swatch.add(new SimpleAttributeModifier("style", background));
  333. item.add(swatch);
  334. if (activeQuery.contains(tq.query)) {
  335. // selected
  336. String q = QueryBuilder.q(activeQuery).remove(tq.query).build();
  337. PageParameters params = queryParameters(q, milestoneParam, statiiParam, assignedToParam, sortBy, desc, 1);
  338. item.add(new LinkPanel("link", "active", tq.name, TicketsPage.class, params).setRenderBodyOnly(true));
  339. Label checked = new Label("checked");
  340. WicketUtils.setCssClass(checked, "iconic-o-x");
  341. item.add(checked);
  342. item.add(new SimpleAttributeModifier("style", background));
  343. } else {
  344. // unselected
  345. String q = QueryBuilder.q(queryParam).toSubquery().and(tq.query).build();
  346. PageParameters params = queryParameters(q, milestoneParam, statiiParam, assignedToParam, sortBy, desc, 1);
  347. item.add(new LinkPanel("link", null, tq.name, TicketsPage.class, params).setRenderBodyOnly(true));
  348. item.add(new Label("checked").setVisible(false));
  349. }
  350. }
  351. };
  352. fragment.add(dynamicQueriesList);
  353. add(fragment);
  354. }
  355. // states
  356. if (ArrayUtils.isEmpty(statiiParam)) {
  357. add(new Label("selectedStatii", getString("gb.all")));
  358. } else {
  359. add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
  360. }
  361. add(new BookmarkablePageLink<Void>("openTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, 1)));
  362. add(new BookmarkablePageLink<Void>("closedTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, 1)));
  363. add(new BookmarkablePageLink<Void>("allTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, 1)));
  364. // by status
  365. List<Status> statii = new ArrayList<Status>(Arrays.asList(Status.values()));
  366. statii.remove(Status.Closed);
  367. ListDataProvider<Status> resolutionsDp = new ListDataProvider<Status>(statii);
  368. DataView<Status> statiiLinks = new DataView<Status>("statii", resolutionsDp) {
  369. private static final long serialVersionUID = 1L;
  370. @Override
  371. public void populateItem(final Item<Status> item) {
  372. final Status status = item.getModelObject();
  373. PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, 1);
  374. String css = TicketsUI.getStatusClass(status);
  375. item.add(new LinkPanel("statusLink", css, status.toString(), TicketsPage.class, p).setRenderBodyOnly(true));
  376. }
  377. };
  378. add(statiiLinks);
  379. // responsible filter
  380. List<TicketResponsible> responsibles = new ArrayList<TicketResponsible>();
  381. for (RegistrantAccessPermission perm : app().repositories().getUserAccessPermissions(getRepositoryModel())) {
  382. if (perm.permission.atLeast(AccessPermission.PUSH)) {
  383. UserModel u = app().users().getUserModel(perm.registrant);
  384. responsibles.add(new TicketResponsible(u));
  385. }
  386. }
  387. Collections.sort(responsibles);
  388. responsibles.add(0, any);
  389. TicketResponsible currentResponsible = null;
  390. for (TicketResponsible u : responsibles) {
  391. if (u.username.equals(assignedToParam)) {
  392. currentResponsible = u;
  393. break;
  394. }
  395. }
  396. add(new Label("currentResponsible", currentResponsible == null ? "" : currentResponsible.displayname));
  397. ListDataProvider<TicketResponsible> responsibleDp = new ListDataProvider<TicketResponsible>(responsibles);
  398. DataView<TicketResponsible> responsibleMenu = new DataView<TicketResponsible>("responsible", responsibleDp) {
  399. private static final long serialVersionUID = 1L;
  400. @Override
  401. public void populateItem(final Item<TicketResponsible> item) {
  402. final TicketResponsible u = item.getModelObject();
  403. PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, u.username, sortBy, desc, 1);
  404. item.add(new LinkPanel("responsibleLink", null, u.displayname, TicketsPage.class, params).setRenderBodyOnly(true));
  405. }
  406. };
  407. add(responsibleMenu);
  408. PageParameters resetResponsibleParams = queryParameters(queryParam, milestoneParam, statiiParam, null, sortBy, desc, 1);
  409. add(new BookmarkablePageLink<Void>("resetResponsible", TicketsPage.class, resetResponsibleParams));
  410. List<TicketSort> sortChoices = new ArrayList<TicketSort>();
  411. sortChoices.add(new TicketSort(getString("gb.sortNewest"), Lucene.created.name(), true));
  412. sortChoices.add(new TicketSort(getString("gb.sortOldest"), Lucene.created.name(), false));
  413. sortChoices.add(new TicketSort(getString("gb.sortMostRecentlyUpdated"), Lucene.updated.name(), true));
  414. sortChoices.add(new TicketSort(getString("gb.sortLeastRecentlyUpdated"), Lucene.updated.name(), false));
  415. sortChoices.add(new TicketSort(getString("gb.sortMostComments"), Lucene.comments.name(), true));
  416. sortChoices.add(new TicketSort(getString("gb.sortLeastComments"), Lucene.comments.name(), false));
  417. sortChoices.add(new TicketSort(getString("gb.sortMostPatchsetRevisions"), Lucene.patchsets.name(), true));
  418. sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
  419. sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
  420. sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));
  421. sortChoices.add(new TicketSort(getString("gb.sortHighestPriority"), Lucene.priority.name(), true));
  422. sortChoices.add(new TicketSort(getString("gb.sortLowestPriority"), Lucene.priority.name(), false));
  423. sortChoices.add(new TicketSort(getString("gb.sortHighestSeverity"), Lucene.severity.name(), true));
  424. sortChoices.add(new TicketSort(getString("gb.sortLowestSeverity"), Lucene.severity.name(), false));
  425. TicketSort currentSort = sortChoices.get(0);
  426. for (TicketSort ts : sortChoices) {
  427. if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
  428. currentSort = ts;
  429. break;
  430. }
  431. }
  432. add(new Label("currentSort", currentSort.name));
  433. ListDataProvider<TicketSort> sortChoicesDp = new ListDataProvider<TicketSort>(sortChoices);
  434. DataView<TicketSort> sortMenu = new DataView<TicketSort>("sort", sortChoicesDp) {
  435. private static final long serialVersionUID = 1L;
  436. @Override
  437. public void populateItem(final Item<TicketSort> item) {
  438. final TicketSort ts = item.getModelObject();
  439. PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, ts.sortBy, ts.desc, 1);
  440. item.add(new LinkPanel("sortLink", null, ts.name, TicketsPage.class, params).setRenderBodyOnly(true));
  441. }
  442. };
  443. add(sortMenu);
  444. // paging links
  445. buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, page, pageSize, results.size(), totalResults);
  446. add(new TicketListPanel("ticketList", results, false, false));
  447. // new milestone link
  448. RepositoryModel repositoryModel = getRepositoryModel();
  449. final boolean acceptingUpdates = app().tickets().isAcceptingTicketUpdates(repositoryModel)
  450. && user != null && user.canAdmin(getRepositoryModel());
  451. if (acceptingUpdates) {
  452. add(new LinkPanel("newMilestone", null, getString("gb.newMilestone"),
  453. NewMilestonePage.class, WicketUtils.newRepositoryParameter(repositoryName)));
  454. } else {
  455. add(new Label("newMilestone").setVisible(false));
  456. }
  457. // milestones list
  458. List<TicketMilestone> openMilestones = new ArrayList<TicketMilestone>();
  459. List<TicketMilestone> closedMilestones = new ArrayList<TicketMilestone>();
  460. for (TicketMilestone milestone : app().tickets().getMilestones(repositoryModel)) {
  461. if (milestone.isOpen()) {
  462. openMilestones.add(milestone);
  463. } else {
  464. closedMilestones.add(milestone);
  465. }
  466. }
  467. Collections.sort(openMilestones, new Comparator<TicketMilestone>() {
  468. @Override
  469. public int compare(TicketMilestone o1, TicketMilestone o2) {
  470. if (o1.due == null) {
  471. return (o2.due == null) ? 0 : 1;
  472. } else if (o2.due == null) {
  473. return -1;
  474. } else {
  475. return o1.due.compareTo(o2.due);
  476. }
  477. }
  478. });
  479. Collections.sort(closedMilestones, new Comparator<TicketMilestone>() {
  480. @Override
  481. public int compare(TicketMilestone o1, TicketMilestone o2) {
  482. if (o1.due == null) {
  483. return (o2.due == null) ? 0 : 1;
  484. } else if (o2.due == null) {
  485. return -1;
  486. } else {
  487. return o1.due.compareTo(o2.due);
  488. }
  489. }
  490. });
  491. DataView<TicketMilestone> openMilestonesList = milestoneList("openMilestonesList", openMilestones, acceptingUpdates);
  492. add(openMilestonesList);
  493. DataView<TicketMilestone> closedMilestonesList = milestoneList("closedMilestonesList", closedMilestones, acceptingUpdates);
  494. add(closedMilestonesList);
  495. }
  496. protected DataView<TicketMilestone> milestoneList(String wicketId, List<TicketMilestone> milestones, final boolean acceptingUpdates) {
  497. ListDataProvider<TicketMilestone> milestonesDp = new ListDataProvider<TicketMilestone>(milestones);
  498. DataView<TicketMilestone> milestonesList = new DataView<TicketMilestone>(wicketId, milestonesDp) {
  499. private static final long serialVersionUID = 1L;
  500. @Override
  501. public void populateItem(final Item<TicketMilestone> item) {
  502. Fragment entryPanel = new Fragment("entryPanel", "milestoneListFragment", this);
  503. item.add(entryPanel);
  504. final TicketMilestone tm = item.getModelObject();
  505. String [] states;
  506. if (tm.isOpen()) {
  507. states = TicketsUI.openStatii;
  508. } else {
  509. states = TicketsUI.closedStatii;
  510. }
  511. PageParameters params = queryParameters(null, tm.name, states, null, null, true, 1);
  512. entryPanel.add(new LinkPanel("milestoneName", null, tm.name, TicketsPage.class, params).setRenderBodyOnly(true));
  513. String css;
  514. String status = tm.status.name();
  515. switch (tm.status) {
  516. case Open:
  517. if (tm.isOverdue()) {
  518. css = "aui-lozenge aui-lozenge-subtle aui-lozenge-error";
  519. status = "overdue";
  520. } else {
  521. css = "aui-lozenge aui-lozenge-subtle";
  522. }
  523. break;
  524. default:
  525. css = "aui-lozenge";
  526. break;
  527. }
  528. Label stateLabel = new Label("milestoneState", status);
  529. WicketUtils.setCssClass(stateLabel, css);
  530. entryPanel.add(stateLabel);
  531. if (tm.due == null) {
  532. entryPanel.add(new Label("milestoneDue", getString("gb.notSpecified")));
  533. } else {
  534. entryPanel.add(WicketUtils.createDatestampLabel("milestoneDue", tm.due, getTimeZone(), getTimeUtils()));
  535. }
  536. if (acceptingUpdates) {
  537. entryPanel.add(new LinkPanel("editMilestone", null, getString("gb.edit"), EditMilestonePage.class,
  538. WicketUtils.newObjectParameter(repositoryName, tm.name)));
  539. } else {
  540. entryPanel.add(new Label("editMilestone").setVisible(false));
  541. }
  542. if (tm.isOpen()) {
  543. // re-load milestone with query results
  544. TicketMilestone m = app().tickets().getMilestone(getRepositoryModel(), tm.name);
  545. Fragment milestonePanel = new Fragment("milestonePanel", "openMilestoneFragment", this);
  546. Label label = new Label("progress");
  547. WicketUtils.setCssStyle(label, "width:" + m.getProgress() + "%;");
  548. milestonePanel.add(label);
  549. milestonePanel.add(new LinkPanel("openTickets", null,
  550. MessageFormat.format(getString("gb.nOpenTickets"), m.getOpenTickets()),
  551. TicketsPage.class,
  552. queryParameters(null, tm.name, TicketsUI.openStatii, null, null, true, 1)));
  553. milestonePanel.add(new LinkPanel("closedTickets", null,
  554. MessageFormat.format(getString("gb.nClosedTickets"), m.getClosedTickets()),
  555. TicketsPage.class,
  556. queryParameters(null, tm.name, TicketsUI.closedStatii, null, null, true, 1)));
  557. milestonePanel.add(new Label("totalTickets", MessageFormat.format(getString("gb.nTotalTickets"), m.getTotalTickets())));
  558. entryPanel.add(milestonePanel);
  559. } else {
  560. entryPanel.add(new Label("milestonePanel").setVisible(false));
  561. }
  562. }
  563. };
  564. return milestonesList;
  565. }
  566. protected PageParameters queryParameters(
  567. String query,
  568. String milestone,
  569. String[] states,
  570. String assignedTo,
  571. String sort,
  572. boolean descending,
  573. int page) {
  574. PageParameters params = WicketUtils.newRepositoryParameter(repositoryName);
  575. if (!StringUtils.isEmpty(query)) {
  576. params.add("q", query);
  577. }
  578. if (!StringUtils.isEmpty(milestone)) {
  579. params.add(Lucene.milestone.name(), milestone);
  580. }
  581. if (!ArrayUtils.isEmpty(states)) {
  582. for (String state : states) {
  583. params.add(Lucene.status.name(), state);
  584. }
  585. }
  586. if (!StringUtils.isEmpty(assignedTo)) {
  587. params.add(Lucene.responsible.name(), assignedTo);
  588. }
  589. if (!StringUtils.isEmpty(sort)) {
  590. params.add("sort", sort);
  591. }
  592. if (!descending) {
  593. params.add("direction", "asc");
  594. }
  595. if (page > 1) {
  596. params.add("pg", "" + page);
  597. }
  598. return params;
  599. }
  600. protected PageParameters newTicketParameter(QueryResult ticket) {
  601. return WicketUtils.newObjectParameter(repositoryName, "" + ticket.number);
  602. }
  603. @Override
  604. protected String getPageName() {
  605. return getString("gb.tickets");
  606. }
  607. protected void buildPager(
  608. final String query,
  609. final String milestone,
  610. final String [] states,
  611. final String assignedTo,
  612. final String sort,
  613. final boolean desc,
  614. final int page,
  615. int pageSize,
  616. int count,
  617. int total) {
  618. boolean showNav = total > (2 * pageSize);
  619. boolean allowPrev = page > 1;
  620. boolean allowNext = (pageSize * (page - 1) + count) < total;
  621. add(new BookmarkablePageLink<Void>("prevLink", TicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page - 1)).setEnabled(allowPrev).setVisible(showNav));
  622. add(new BookmarkablePageLink<Void>("nextLink", TicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page + 1)).setEnabled(allowNext).setVisible(showNav));
  623. if (total <= pageSize) {
  624. add(new Label("pageLink").setVisible(false));
  625. return;
  626. }
  627. // determine page numbers to display
  628. int pages = count == 0 ? 0 : ((total / pageSize) + (total % pageSize == 0 ? 0 : 1));
  629. // preferred number of pagelinks
  630. int segments = 5;
  631. if (pages < segments) {
  632. // not enough data for preferred number of page links
  633. segments = pages;
  634. }
  635. int minpage = Math.min(Math.max(1, page - 2), pages - (segments - 1));
  636. int maxpage = Math.min(pages, minpage + (segments - 1));
  637. List<Integer> sequence = new ArrayList<Integer>();
  638. for (int i = minpage; i <= maxpage; i++) {
  639. sequence.add(i);
  640. }
  641. ListDataProvider<Integer> pagesDp = new ListDataProvider<Integer>(sequence);
  642. DataView<Integer> pagesView = new DataView<Integer>("pageLink", pagesDp) {
  643. private static final long serialVersionUID = 1L;
  644. @Override
  645. public void populateItem(final Item<Integer> item) {
  646. final Integer i = item.getModelObject();
  647. LinkPanel link = new LinkPanel("page", null, "" + i, TicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, i));
  648. link.setRenderBodyOnly(true);
  649. if (i == page) {
  650. WicketUtils.setCssClass(item, "active");
  651. }
  652. item.add(link);
  653. }
  654. };
  655. add(pagesView);
  656. }
  657. }