Paul Martin
2014-10-18 f5d5680b85ff5df5a0a9fc8cf7180401fd4455b2
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));
233
234         TicketSort currentSort = sortChoices.get(0);
235         for (TicketSort ts : sortChoices) {
236             if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
237                 currentSort = ts;
238                 break;
239             }
240         }
241         add(new Label("currentSort", currentSort.name));
242
243         ListDataProvider<TicketSort> sortChoicesDp = new ListDataProvider<TicketSort>(sortChoices);
244         DataView<TicketSort> sortMenu = new DataView<TicketSort>("sort", sortChoicesDp) {
245             private static final long serialVersionUID = 1L;
246
247             @Override
248             public void populateItem(final Item<TicketSort> item) {
249                 final TicketSort ts = item.getModelObject();
250                 PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, ts.sortBy, ts.desc, 1);
251                 item.add(new LinkPanel("sortLink", null, ts.name, MyTicketsPage.class, params).setRenderBodyOnly(true));
252             }
253         };
254         add(sortMenu);
b3f70e 255
e7f65d 256         // Build Query here
H 257         QueryBuilder qb = new QueryBuilder(queryParam);
258         if (!qb.containsField(Lucene.status.name()) && !ArrayUtils.isEmpty(statiiParam)) {
259             // specify the states
260             boolean not = false;
261             QueryBuilder q = new QueryBuilder();
262             for (String state : statiiParam) {
263                 if (state.charAt(0) == '!') {
264                     not = true;
265                     q.and(Lucene.status.doesNotMatch(state.substring(1)));
266                 } else {
267                     q.or(Lucene.status.matches(state));
268                 }
269             }
270             if (not) {
271                 qb.and(q.toString());
272             } else {
273                 qb.and(q.toSubquery().toString());
274             }
275         }
b3f70e 276
JM 277         final String luceneQuery;
278         if (qb.containsField(Lucene.createdby.name())
279                 || qb.containsField(Lucene.responsible.name())
280                 || qb.containsField(Lucene.watchedby.name())) {
281             // focused "my tickets" query
282             luceneQuery = qb.build();
283         } else {
284             // general "my tickets" query
285             QueryBuilder myQuery = new QueryBuilder();
286             myQuery.or(Lucene.createdby.matches(username));
287             myQuery.or(Lucene.responsible.matches(username));
288             myQuery.or(Lucene.watchedby.matches(username));
289             myQuery.and(qb.toSubquery().toString());
290             luceneQuery = myQuery.build();
291         }
292
3f5b8f 293         // paging links
H 294         int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1;
295         int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25);
b3f70e 296
3f5b8f 297         List<QueryResult> results;
fdd82f 298         if(StringUtils.isEmpty(searchParam)) {
JM 299             results = app().tickets().queryFor(luceneQuery, page, pageSize, sortBy, desc);
300         } else {
301             results = app().tickets().searchFor(null, searchParam, page, pageSize);
3f5b8f 302         }
fdd82f 303
3f5b8f 304         int totalResults = results.size() == 0 ? 0 : results.get(0).totalResults;
H 305         buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, page, pageSize, results.size(), totalResults);
b3f70e 306
4062e4 307         final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
fdd82f 308         add(new TicketListPanel("ticketList", results, showSwatch, true));
e7f65d 309     }
H 310
311     protected PageParameters queryParameters(
312             String query,
313             String milestone,
314             String[] states,
315             String assignedTo,
316             String sort,
317             boolean descending,
318             int page) {
319
320         PageParameters params = WicketUtils.newRepositoryParameter("");
321         if (!StringUtils.isEmpty(query)) {
322             params.add("q", query);
323         }
324         if (!StringUtils.isEmpty(milestone)) {
325             params.add(Lucene.milestone.name(), milestone);
326         }
327         if (!ArrayUtils.isEmpty(states)) {
328             for (String state : states) {
329                 params.add(Lucene.status.name(), state);
330             }
331         }
332         if (!StringUtils.isEmpty(assignedTo)) {
333             params.add(Lucene.responsible.name(), assignedTo);
334         }
335         if (!StringUtils.isEmpty(sort)) {
336             params.add("sort", sort);
337         }
338         if (!descending) {
339             params.add("direction", "asc");
340         }
341         if (page > 1) {
342             params.add("pg", "" + page);
343         }
344         return params;
345     }
346
347     protected void buildPager(
348             final String query,
349             final String milestone,
350             final String [] states,
351             final String assignedTo,
352             final String sort,
353             final boolean desc,
354             final int page,
355             int pageSize,
356             int count,
357             int total) {
358
359         boolean showNav = total > (2 * pageSize);
360         boolean allowPrev = page > 1;
361         boolean allowNext = (pageSize * (page - 1) + count) < total;
3f5b8f 362         add(new BookmarkablePageLink<Void>("prevLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page - 1)).setEnabled(allowPrev).setVisible(showNav));
H 363         add(new BookmarkablePageLink<Void>("nextLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page + 1)).setEnabled(allowNext).setVisible(showNav));
e7f65d 364
H 365         if (total <= pageSize) {
366             add(new Label("pageLink").setVisible(false));
367             return;
368         }
369
370         // determine page numbers to display
371         int pages = count == 0 ? 0 : ((total / pageSize) + (total % pageSize == 0 ? 0 : 1));
372         // preferred number of pagelinks
373         int segments = 5;
374         if (pages < segments) {
375             // not enough data for preferred number of page links
376             segments = pages;
377         }
378         int minpage = Math.min(Math.max(1, page - 2), pages - (segments - 1));
379         int maxpage = Math.min(pages, minpage + (segments - 1));
380         List<Integer> sequence = new ArrayList<Integer>();
381         for (int i = minpage; i <= maxpage; i++) {
382             sequence.add(i);
383         }
384
385         ListDataProvider<Integer> pagesDp = new ListDataProvider<Integer>(sequence);
386         DataView<Integer> pagesView = new DataView<Integer>("pageLink", pagesDp) {
387             private static final long serialVersionUID = 1L;
388
389             @Override
390             public void populateItem(final Item<Integer> item) {
391                 final Integer i = item.getModelObject();
3f5b8f 392                 LinkPanel link = new LinkPanel("page", null, "" + i, MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, i));
e7f65d 393                 link.setRenderBodyOnly(true);
H 394                 if (i == page) {
395                     WicketUtils.setCssClass(item, "active");
396                 }
397                 item.add(link);
398             }
399         };
400         add(pagesView);
4062e4 401     }
338037 402 }