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