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