Paul Martin
2016-04-05 6ecf3907a012f5c12c6801f0d2ffe8b4e440c209
commit | author | age
fdd82f 1 /*
JM 2  * Copyright 2014 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  */
338037 16 package com.gitblit.wicket.pages;
BC 17
e7f65d 18 import java.util.ArrayList;
H 19 import java.util.Arrays;
3004fe 20 import java.util.Collection;
EY 21 import java.util.Collections;
22 import java.util.HashMap;
338037 23 import java.util.List;
BC 24
e1fa9b 25 import org.apache.wicket.PageParameters;
JM 26 import org.apache.wicket.markup.html.basic.Label;
e7f65d 27 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
e1fa9b 28 import org.apache.wicket.markup.repeater.Item;
JM 29 import org.apache.wicket.markup.repeater.data.DataView;
30 import org.apache.wicket.markup.repeater.data.ListDataProvider;
31
e7f65d 32 import com.gitblit.Keys;
dcb6c5 33 import com.gitblit.models.RepositoryModel;
338037 34 import com.gitblit.models.TicketModel;
e1fa9b 35 import com.gitblit.models.TicketModel.Status;
b3f70e 36 import com.gitblit.models.UserModel;
e1fa9b 37 import com.gitblit.tickets.QueryBuilder;
JM 38 import com.gitblit.tickets.QueryResult;
39 import com.gitblit.tickets.TicketIndexer.Lucene;
e7f65d 40 import com.gitblit.utils.ArrayUtils;
e1fa9b 41 import com.gitblit.utils.StringUtils;
338037 42 import com.gitblit.wicket.GitBlitWebSession;
fdd82f 43 import com.gitblit.wicket.TicketsUI;
JM 44 import com.gitblit.wicket.TicketsUI.TicketSort;
75796a 45 import com.gitblit.wicket.WicketUtils;
338037 46 import com.gitblit.wicket.panels.LinkPanel;
fdd82f 47 import com.gitblit.wicket.panels.TicketListPanel;
JM 48 import com.gitblit.wicket.panels.TicketSearchForm;
49 import com.gitblit.wicket.panels.UserTitlePanel;
338037 50
fdd82f 51 /**
JM 52  * My Tickets page
53  *
54  * @author Christian Buisson
55  * @author James Moger
56  */
338037 57 public class MyTicketsPage extends RootPage {
e7f65d 58
3004fe 59     public MyTicketsPage() {
EY 60         this(null);
61     }
b3f70e 62
3004fe 63     public MyTicketsPage(PageParameters params) {
EY 64         super(params);
65         setupPage("", getString("gb.myTickets"));
b3f70e 66
3004fe 67         UserModel currentUser = GitBlitWebSession.get().getUser();
EY 68         if (currentUser == null || UserModel.ANONYMOUS.equals(currentUser)) {
69             setRedirect(true);
70             setResponsePage(getApplication().getHomePage());
71             return;
72         }
b3f70e 73
3004fe 74         final String username = currentUser.getName();
4062e4 75
3004fe 76         final String[] statiiParam = (params == null) ? TicketsUI.openStatii : params.getStringArray(Lucene.status.name());
EY 77         final String assignedToParam = (params == null) ? "" : params.getString(Lucene.responsible.name(), null);
78         final String milestoneParam = (params == null) ? "" : params.getString(Lucene.milestone.name(), null);
c33ac1 79         final String queryParam = (params == null) ? null : params.getString("q", null);
3004fe 80         final String searchParam = (params == null) ? "" : params.getString("s", null);
EY 81         final String sortBy = (params == null) ? "" : Lucene.fromString(params.getString("sort", Lucene.created.name())).name();
82         final String repositoryId = (params == null) ? "" : params.getString(Lucene.rid.name(), null);
83         final boolean desc = (params == null) ? true : !"asc".equals(params.getString("direction", "desc"));
b3f70e 84
JM 85
3004fe 86         // add the user title panel
EY 87         add(new UserTitlePanel("userTitlePanel", currentUser, getString("gb.myTickets")));
88
89         // add search form
90         add(new TicketSearchForm("ticketSearchForm", null, searchParam, getClass(), params));
91
92         // standard queries
93         add(new BookmarkablePageLink<Void>("changesQuery", MyTicketsPage.class,
94                 queryParameters(
95                         Lucene.type.matches(TicketModel.Type.Proposal.name()),
96                         milestoneParam,
97                         statiiParam,
98                         assignedToParam,
99                         sortBy,
100                         desc,
4cb1a9 101                         repositoryId,
3004fe 102                         1)));
e7f65d 103
3004fe 104         add(new BookmarkablePageLink<Void>("bugsQuery", MyTicketsPage.class,
EY 105                 queryParameters(
106                         Lucene.type.matches(TicketModel.Type.Bug.name()),
107                         milestoneParam,
108                         statiiParam,
109                         assignedToParam,
110                         sortBy,
111                         desc,
4cb1a9 112                         repositoryId,
3004fe 113                         1)));
e7f65d 114
3004fe 115         add(new BookmarkablePageLink<Void>("enhancementsQuery", MyTicketsPage.class,
EY 116                 queryParameters(
117                         Lucene.type.matches(TicketModel.Type.Enhancement.name()),
118                         milestoneParam,
119                         statiiParam,
120                         assignedToParam,
121                         sortBy,
122                         desc,
4cb1a9 123                         repositoryId,
3004fe 124                         1)));
e7f65d 125
3004fe 126         add(new BookmarkablePageLink<Void>("tasksQuery", MyTicketsPage.class,
EY 127                 queryParameters(
128                         Lucene.type.matches(TicketModel.Type.Task.name()),
129                         milestoneParam,
130                         statiiParam,
131                         assignedToParam,
132                         sortBy,
133                         desc,
4cb1a9 134                         repositoryId,
3004fe 135                         1)));
e7f65d 136
3004fe 137         add(new BookmarkablePageLink<Void>("questionsQuery", MyTicketsPage.class,
EY 138                 queryParameters(
139                         Lucene.type.matches(TicketModel.Type.Question.name()),
140                         milestoneParam,
141                         statiiParam,
142                         assignedToParam,
143                         sortBy,
144                         desc,
4cb1a9 145                         repositoryId,
3004fe 146                         1)));
EY 147         
148         add(new BookmarkablePageLink<Void>("maintenanceQuery", MyTicketsPage.class,
149                 queryParameters(
150                         Lucene.type.matches(TicketModel.Type.Maintenance.name()),
151                         milestoneParam,
152                         statiiParam,
153                         assignedToParam,
154                         sortBy,
155                         desc,
4cb1a9 156                         repositoryId,
3004fe 157                         1)));
e7f65d 158
3004fe 159         add(new BookmarkablePageLink<Void>("resetQuery", MyTicketsPage.class,
EY 160                 queryParameters(
dcb6c5 161                         null,
3004fe 162                         milestoneParam,
EY 163                         TicketsUI.openStatii,
164                         null,
165                         null,
166                         true,
167                         null,
168                         1)));
e7f65d 169
3004fe 170         add(new Label("userDivider"));
EY 171         add(new BookmarkablePageLink<Void>("createdQuery", MyTicketsPage.class,
172                 queryParameters(
173                         Lucene.createdby.matches(username),
174                         milestoneParam,
175                         statiiParam,
176                         assignedToParam,
177                         sortBy,
178                         desc,
4cb1a9 179                         repositoryId,
3004fe 180                         1)));
e7f65d 181
3004fe 182         add(new BookmarkablePageLink<Void>("watchedQuery", MyTicketsPage.class,
EY 183                 queryParameters(
184                         Lucene.watchedby.matches(username),
185                         milestoneParam,
186                         statiiParam,
187                         assignedToParam,
188                         sortBy,
189                         desc,
4cb1a9 190                         repositoryId,
3004fe 191                         1)));
EY 192         add(new BookmarkablePageLink<Void>("mentionsQuery", MyTicketsPage.class,
193                 queryParameters(
194                         Lucene.mentions.matches(username),
195                         milestoneParam,
196                         statiiParam,
197                         assignedToParam,
198                         sortBy,
199                         desc,
4cb1a9 200                         repositoryId,
3004fe 201                         1)));
EY 202         add(new BookmarkablePageLink<Void>("responsibleQuery", MyTicketsPage.class,
203                 queryParameters(
204                         Lucene.responsible.matches(username),
205                         milestoneParam,
206                         statiiParam,
207                         assignedToParam,
208                         sortBy,
209                         desc,
4cb1a9 210                         repositoryId,
3004fe 211                         1)));
b3f70e 212
3004fe 213         // states
EY 214         if (ArrayUtils.isEmpty(statiiParam)) {
215             add(new Label("selectedStatii", getString("gb.all")));
216         } else {
217             add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
218         }
219         add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, repositoryId, 1)));
220         add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, repositoryId, 1)));
221         add(new BookmarkablePageLink<Void>("allTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, repositoryId, 1)));
b3f70e 222
3004fe 223         // by status
EY 224         List<Status> statii = new ArrayList<Status>(Arrays.asList(Status.values()));
225         statii.remove(Status.Closed);
226         ListDataProvider<Status> resolutionsDp = new ListDataProvider<Status>(statii);
227         DataView<Status> statiiLinks = new DataView<Status>("statii", resolutionsDp) {
228             private static final long serialVersionUID = 1L;
e7f65d 229
3004fe 230             @Override
EY 231             public void populateItem(final Item<Status> item) {
232                 final Status status = item.getModelObject();
233                 PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, repositoryId, 1);
234                 String css = TicketsUI.getStatusClass(status);
235                 item.add(new LinkPanel("statusLink", css, status.toString(), MyTicketsPage.class, p).setRenderBodyOnly(true));
236             }
237         };
238         add(statiiLinks);
e7f65d 239
dcb6c5 240         // by sort
3004fe 241         List<TicketSort> sortChoices = new ArrayList<TicketSort>();
EY 242         sortChoices.add(new TicketSort(getString("gb.sortNewest"), Lucene.created.name(), true));
243         sortChoices.add(new TicketSort(getString("gb.sortOldest"), Lucene.created.name(), false));
244         sortChoices.add(new TicketSort(getString("gb.sortMostRecentlyUpdated"), Lucene.updated.name(), true));
245         sortChoices.add(new TicketSort(getString("gb.sortLeastRecentlyUpdated"), Lucene.updated.name(), false));
246         sortChoices.add(new TicketSort(getString("gb.sortMostComments"), Lucene.comments.name(), true));
247         sortChoices.add(new TicketSort(getString("gb.sortLeastComments"), Lucene.comments.name(), false));
248         sortChoices.add(new TicketSort(getString("gb.sortMostPatchsetRevisions"), Lucene.patchsets.name(), true));
249         sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
250         sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
251         sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));
252         sortChoices.add(new TicketSort(getString("gb.sortHighestPriority"), Lucene.priority.name(), true));
253         sortChoices.add(new TicketSort(getString("gb.sortLowestPriority"), Lucene.priority.name(), false));
254         sortChoices.add(new TicketSort(getString("gb.sortHighestSeverity"), Lucene.severity.name(), true));
255         sortChoices.add(new TicketSort(getString("gb.sortLowestSeverity"), Lucene.severity.name(), false));
e7f65d 256
3004fe 257         TicketSort currentSort = sortChoices.get(0);
EY 258         for (TicketSort ts : sortChoices) {
259             if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
260                 currentSort = ts;
261                 break;
262             }
263         }
264         add(new Label("currentSort", currentSort.name));
e7f65d 265
3004fe 266         ListDataProvider<TicketSort> sortChoicesDp = new ListDataProvider<TicketSort>(sortChoices);
EY 267         DataView<TicketSort> sortMenu = new DataView<TicketSort>("sort", sortChoicesDp) {
268             private static final long serialVersionUID = 1L;
e7f65d 269
3004fe 270             @Override
EY 271             public void populateItem(final Item<TicketSort> item) {
272                 final TicketSort ts = item.getModelObject();
273                 PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, ts.sortBy, ts.desc, repositoryId, 1);
274                 item.add(new LinkPanel("sortLink", null, ts.name, MyTicketsPage.class, params).setRenderBodyOnly(true));
275             }
276         };
277         add(sortMenu);
dcb6c5 278
EY 279         // by repository
3004fe 280         final List<QueryResult> tickets =
c33ac1 281             query(initializeQueryBuilder(null, username), 1, Integer.MAX_VALUE, sortBy, desc);
3004fe 282         final List<RepositoryModel> repositoryChoices = correspondingRepositories(tickets);
c33ac1 283         Collections.sort(repositoryChoices);
3004fe 284
EY 285         final RepositoryModel noneChoice = new RepositoryModel();
4cb1a9 286         noneChoice.name = getString("gb.all");
710620 287         repositoryChoices.add(0, noneChoice);
dcb6c5 288
c33ac1 289         RepositoryModel currentRepository = repositoryChoices.get(0);
3004fe 290         for (RepositoryModel r : repositoryChoices) {
EY 291             if (r.getRID().equals(repositoryId)) {
292                 currentRepository = r;
293                 break;
294             }
295         }
c33ac1 296         add(new Label("currentRepository", currentRepository.toString()));
dcb6c5 297
3004fe 298         ListDataProvider<RepositoryModel> repositoryChoicesDp = new ListDataProvider<RepositoryModel>(repositoryChoices);
EY 299         DataView<RepositoryModel> repositoryMenu = new DataView<RepositoryModel>("repository", repositoryChoicesDp) {
300             private static final long serialVersionUID = 1L;
b3f70e 301
3004fe 302             @Override
EY 303             public void populateItem(final Item<RepositoryModel> item) {
304                 final RepositoryModel r = item.getModelObject();
c33ac1 305                 String rid = r == noneChoice ? null : r.getRID();
EY 306                 PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, rid, 1);
3004fe 307                 item.add(new LinkPanel("repositoryLink", null, r.toString(), MyTicketsPage.class, params).setRenderBodyOnly(true));
EY 308             }
309         };
310         add(repositoryMenu);
311
312         // Update query with filter criteria
313         final QueryBuilder qb = initializeQueryBuilder(queryParam, username);
314
315         if (!qb.containsField(Lucene.status.name()) && !ArrayUtils.isEmpty(statiiParam)) {
316             // specify the states
317             boolean not = false;
318             QueryBuilder q = new QueryBuilder();
319             for (String state : statiiParam) {
320                 if (state.charAt(0) == '!') {
321                     not = true;
322                     q.and(Lucene.status.doesNotMatch(state.substring(1)));
323                 } else {
324                     q.or(Lucene.status.matches(state));
325                 }
326             }
327
328             if (not) {
329                 qb.and(q.toString());
330             } else {
331                 qb.and(q.toSubquery().toString());
332             }
333         }
531abe 334
4cb1a9 335         if (noneChoice != currentRepository && !qb.containsField(Lucene.rid.name())) {
710620 336             QueryBuilder q1 = new QueryBuilder();
4cb1a9 337             q1.and(Lucene.rid.matches(repositoryId));
710620 338             qb.and(q1.toSubquery().toString());
EY 339         }
b3f70e 340
3004fe 341         // paging links
EY 342         int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1;
343         int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25);
b3f70e 344
6e2934 345         final List<QueryResult> allResults = 
3004fe 346             StringUtils.isEmpty(searchParam) ? query(qb, page, pageSize, sortBy, desc) : search(searchParam, page, pageSize);
b3f70e 347
6e2934 348         List<QueryResult> viewableResults = new ArrayList<>(allResults.size());
PM 349         for (QueryResult queryResult : allResults) {
350             RepositoryModel model = app().repositories().getRepositoryModel(currentUser, queryResult.repository);
351             
352             if ((model != null) && (currentUser.canView(model))) {
353                 viewableResults.add(queryResult);
354             }
355         }
356             
357         int totalResults = viewableResults.size() == 0 ? 0 : viewableResults.get(0).totalResults;
358         buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, repositoryId, page, pageSize, viewableResults.size(), totalResults);
fdd82f 359
3004fe 360         final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
6e2934 361         add(new TicketListPanel("ticketList", viewableResults, showSwatch, true));
3004fe 362     }
b3f70e 363
3004fe 364     protected PageParameters queryParameters(
EY 365             String query,
366             String milestone,
367             String[] states,
368             String assignedTo,
369             String sort,
370             boolean descending,
371             String repositoryId,
372             int page) {
e7f65d 373
3004fe 374         PageParameters params = WicketUtils.newRepositoryParameter("");
EY 375         if (!StringUtils.isEmpty(query)) {
376             params.add("q", query);
377         }
378         if (!StringUtils.isEmpty(milestone)) {
379             params.add(Lucene.milestone.name(), milestone);
380         }
381         if (!ArrayUtils.isEmpty(states)) {
382             for (String state : states) {
383                 params.add(Lucene.status.name(), state);
384             }
385         }
386         if (!StringUtils.isEmpty(assignedTo)) {
387             params.add(Lucene.responsible.name(), assignedTo);
388         }
389         if (!StringUtils.isEmpty(sort)) {
390             params.add("sort", sort);
391         }
392         if (!descending) {
393             params.add("direction", "asc");
394         }
395         if (!StringUtils.isEmpty(repositoryId)) {
396             params.add(Lucene.rid.name(), repositoryId);
397         }
398         if (page > 1) {
399             params.add("pg", "" + page);
400         }
401         return params;
402     }
e7f65d 403
3004fe 404     protected void buildPager(
EY 405             final String query,
406             final String milestone,
407             final String [] states,
408             final String assignedTo,
409             final String sort,
410             final boolean desc,
411             final String repositoryId,
412             final int page,
413             int pageSize,
414             int count,
415             int total) {
e7f65d 416
3004fe 417         boolean showNav = total > (2 * pageSize);
EY 418         boolean allowPrev = page > 1;
419         boolean allowNext = (pageSize * (page - 1) + count) < total;
420         add(new BookmarkablePageLink<Void>("prevLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, page - 1)).setEnabled(allowPrev).setVisible(showNav));
421         add(new BookmarkablePageLink<Void>("nextLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, page + 1)).setEnabled(allowNext).setVisible(showNav));
e7f65d 422
3004fe 423         if (total <= pageSize) {
EY 424             add(new Label("pageLink").setVisible(false));
425             return;
426         }
e7f65d 427
3004fe 428         // determine page numbers to display
EY 429         int pages = count == 0 ? 0 : ((total / pageSize) + (total % pageSize == 0 ? 0 : 1));
430         // preferred number of pagelinks
431         int segments = 5;
432         if (pages < segments) {
433             // not enough data for preferred number of page links
434             segments = pages;
435         }
436         int minpage = Math.min(Math.max(1, page - 2), pages - (segments - 1));
437         int maxpage = Math.min(pages, minpage + (segments - 1));
438         List<Integer> sequence = new ArrayList<Integer>();
439         for (int i = minpage; i <= maxpage; i++) {
440             sequence.add(i);
441         }
e7f65d 442
3004fe 443         ListDataProvider<Integer> pagesDp = new ListDataProvider<Integer>(sequence);
EY 444         DataView<Integer> pagesView = new DataView<Integer>("pageLink", pagesDp) {
445             private static final long serialVersionUID = 1L;
e7f65d 446
3004fe 447             @Override
EY 448             public void populateItem(final Item<Integer> item) {
449                 final Integer i = item.getModelObject();
450                 LinkPanel link = new LinkPanel("page", null, "" + i, MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, i));
451                 link.setRenderBodyOnly(true);
452                 if (i == page) {
453                     WicketUtils.setCssClass(item, "active");
454                 }
455                 item.add(link);
456             }
457         };
458         add(pagesView);
459     }
e7f65d 460
3004fe 461     private QueryBuilder initializeQueryBuilder(String queryparam, String username) {
EY 462         final QueryBuilder qb = new QueryBuilder(queryparam);
463
464         // focused "my tickets"
465         if (qb.containsField(Lucene.createdby.name())
466                 || qb.containsField(Lucene.responsible.name())
c33ac1 467                 || qb.containsField(Lucene.watchedby.name())
EY 468                 || qb.containsField(Lucene.mentions.name())) {
3004fe 469
EY 470             return qb;
471         }
472
473         // general "my tickets"
474         return qb.andSubquery()
475                  .or(Lucene.createdby.matches(username))
476                  .or(Lucene.responsible.matches(username))
477                  .or(Lucene.watchedby.matches(username))
c33ac1 478                  .or(Lucene.mentions.matches(username))
3004fe 479                  .endSubquery();
EY 480     }
481
482     private List<QueryResult> query(QueryBuilder qb, int page, int pageSize, String sortBy, boolean descending) {
483         return app().tickets().queryFor(qb.build(), page, pageSize, sortBy, descending);
484     }
485
486     private List<QueryResult> search(String searchParam, int page, int pageSize) {
487         return app().tickets().searchFor(null, searchParam, page, pageSize);
488     }
489
490     private List<RepositoryModel> correspondingRepositories(Collection<QueryResult> tickets) { 
491         final HashMap<String, RepositoryModel> result = new HashMap<>();
492         for (QueryResult ticket : tickets) {
493             RepositoryModel repository = app().repositories().getRepositoryModel(ticket.repository);
494             if (!result.containsKey(repository.getRID())) {
495                 result.put(repository.getRID(), repository);
496             }
497         }
498
499         return new ArrayList<>(result.values());
500     }
338037 501 }