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