* @param oldName
* @param newName
* @param createdBy
- * @param send ticket notifications
+ * @param notifyOpenTickets
* @return true if successful
* @since 1.6.0
*/
- public synchronized boolean renameMilestone(RepositoryModel repository, String oldName, String newName, String createdBy, boolean notify) {
+ public synchronized boolean renameMilestone(RepositoryModel repository, String oldName,
+ String newName, String createdBy, boolean notifyOpenTickets) {
if (StringUtils.isEmpty(newName)) {
throw new IllegalArgumentException("new milestone can not be empty!");
}
Change change = new Change(createdBy);
change.setField(Field.milestone, newName);
TicketModel ticket = updateTicket(repository, qr.number, change);
- if (notify && ticket.isOpen()) {
+ if (notifyOpenTickets && ticket.isOpen()) {
notifier.queueMailing(ticket);
}
}
- if (notify) {
+ if (notifyOpenTickets) {
notifier.sendAll();
}
* @since 1.4.0
*/
public synchronized boolean deleteMilestone(RepositoryModel repository, String milestone, String createdBy) {
+ return deleteMilestone(repository, milestone, createdBy, true);
+ }
+
+ /**
+ * Deletes a milestone.
+ *
+ * @param repository
+ * @param milestone
+ * @param createdBy
+ * @param notifyOpenTickets
+ * @return true if successful
+ * @since 1.6.0
+ */
+ public synchronized boolean deleteMilestone(RepositoryModel repository, String milestone,
+ String createdBy, boolean notifyOpenTickets) {
if (StringUtils.isEmpty(milestone)) {
throw new IllegalArgumentException("milestone can not be empty!");
}
milestonesCache.remove(repository.name);
+ TicketNotifier notifier = createNotifier();
for (QueryResult qr : tm.tickets) {
- if (qr.isOpen()) {
- // reset the milestone only for open tickets
- Change change = new Change(createdBy);
- change.setField(Field.milestone, "");
- TicketModel ticket = updateTicket(repository, qr.number, change);
+ Change change = new Change(createdBy);
+ change.setField(Field.milestone, "");
+ TicketModel ticket = updateTicket(repository, qr.number, change);
+ if (notifyOpenTickets && ticket.isOpen()) {
+ notifier.queueMailing(ticket);
}
}
+ if (notifyOpenTickets) {
+ notifier.sendAll();
+ }
return true;
} catch (IOException e) {
log.error("failed to delete milestone " + milestone + " in " + repository, e);
super(name);
status = Status.Open;
}
-
+
+ public boolean isOpen() {
+ return status == Status.Open;
+ }
+
+ public boolean isOverdue() {
+ return due == null ? false : System.currentTimeMillis() > due.getTime();
+ }
+
public void setDue(Date due) {
this.due = due;
}
gb.anonymousCanNotPropose = Anonymous users can not propose patchsets.
gb.youDoNotHaveClonePermission = You are not permitted to clone this repository.
gb.newMilestone = new milestone
-gb.editMilestone = edit milestone
\ No newline at end of file
+gb.editMilestone = edit milestone
+gb.notifyChangedOpenTickets = send notification for changed open tickets
+gb.overdue = overdue
\ No newline at end of file
<!-- Edit Milestone Table -->\r
<table class="ticket">\r
<tr><th><wicket:message key="gb.milestone"></wicket:message></th><td class="edit"><input class="input-large" type="text" wicket:id="name" id="name"></input></td></tr>\r
- <tr><th><wicket:message key="gb.due"></wicket:message></th><td class="edit"><input class="input-large" type="text" wicket:id="due"></input></td></tr>\r
+ <tr><th><wicket:message key="gb.due"></wicket:message></th><td class="edit"><input class="input-large" type="text" wicket:id="due"></input> <span class="help-inline" wicket:id="dueFormat"></span></td></tr>\r
<tr><th><wicket:message key="gb.status"></wicket:message><span style="color:red;">*</span></th><td class="edit"><select class="input-large" wicket:id="status"></select></td></tr>\r
+ <tr><th></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="notify" /> <span class="help-inline"><wicket:message key="gb.notifyChangedOpenTickets"></wicket:message></span></label></td></tr>\r
</table>\r
</div>\r
</div> \r
import org.apache.wicket.ajax.AjaxRequestTarget;\r
import org.apache.wicket.ajax.markup.html.form.AjaxButton;\r
import org.apache.wicket.extensions.markup.html.form.DateTextField;\r
+import org.apache.wicket.markup.html.basic.Label;\r
import org.apache.wicket.markup.html.form.Button;\r
+import org.apache.wicket.markup.html.form.CheckBox;\r
import org.apache.wicket.markup.html.form.DropDownChoice;\r
import org.apache.wicket.markup.html.form.Form;\r
import org.apache.wicket.markup.html.form.TextField;\r
\r
form.add(new TextField<String>("name", nameModel));\r
form.add(new DateTextField("due", dueModel, "yyyy-MM-dd"));\r
+ form.add(new Label("dueFormat", "yyyy-MM-dd"));\r
+ form.add(new CheckBox("notify", notificationModel));\r
\r
List<Status> statusChoices = Arrays.asList(Status.Open, Status.Closed);\r
form.add(new DropDownChoice<TicketModel.Status>("status", statusModel, statusChoices));\r
public void onSubmit() {\r
UserModel currentUser = GitBlitWebSession.get().getUser();\r
String createdBy = currentUser.username;\r
+ boolean notify = notificationModel.getObject();\r
\r
- if (app().tickets().deleteMilestone(getRepositoryModel(), oldName, createdBy)) {\r
+ if (app().tickets().deleteMilestone(getRepositoryModel(), oldName, createdBy, notify)) {\r
setResponsePage(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));\r
} else {\r
// TODO error processing\r
<!-- New Milestone Table -->\r
<table class="ticket">\r
<tr><th><wicket:message key="gb.milestone"></wicket:message></th><td class="edit"><input class="input-large" type="text" wicket:id="name" id="name"></input></td></tr>\r
- <tr><th><wicket:message key="gb.due"></wicket:message></th><td class="edit"><input class="input-large" type="text" wicket:id="due"></input></td></tr>\r
+ <tr><th><wicket:message key="gb.due"></wicket:message></th><td class="edit"><input class="input-large" type="text" wicket:id="due"></input> <span class="help-inline" wicket:id="dueFormat"></span></td></tr>\r
</table>\r
</div>\r
</div> \r
import org.apache.wicket.ajax.AjaxRequestTarget;\r
import org.apache.wicket.ajax.markup.html.form.AjaxButton;\r
import org.apache.wicket.extensions.markup.html.form.DateTextField;\r
+import org.apache.wicket.markup.html.basic.Label;\r
import org.apache.wicket.markup.html.form.Button;\r
import org.apache.wicket.markup.html.form.Form;\r
import org.apache.wicket.markup.html.form.TextField;\r
\r
form.add(new TextField<String>("name", nameModel));\r
form.add(new DateTextField("due", dueModel, "yyyy-MM-dd"));\r
+ form.add(new Label("dueFormat", "yyyy-MM-dd"));\r
\r
form.add(new AjaxButton("create") {\r
\r
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {\r
String name = nameModel.getObject();\r
if (StringUtils.isEmpty(name)) {\r
+ // invalid name\r
+ return;\r
+ }\r
+\r
+ TicketMilestone milestone = app().tickets().getMilestone(getRepositoryModel(), name);\r
+ if (milestone != null) {\r
+ // milestone already exists\r
return;\r
}\r
\r
UserModel currentUser = GitBlitWebSession.get().getUser();\r
String createdBy = currentUser.username;\r
\r
- TicketMilestone milestone = app().tickets().createMilestone(getRepositoryModel(), name, createdBy);\r
+ milestone = app().tickets().createMilestone(getRepositoryModel(), name, createdBy);\r
if (milestone != null) {\r
milestone.due = due;\r
app().tickets().updateMilestone(getRepositoryModel(), milestone, createdBy);\r
import java.util.ArrayList;\r
import java.util.Arrays;\r
import java.util.Collections;\r
+import java.util.Comparator;\r
import java.util.List;\r
import java.util.Set;\r
import java.util.TreeSet;\r
} else {\r
add(new Label("newMilestone").setVisible(false));\r
}\r
- \r
+\r
// milestones list\r
- List<TicketMilestone> allMilestones = app().tickets().getMilestones(repositoryModel);\r
+ List<TicketMilestone> allMilestones = new ArrayList<TicketMilestone>(app().tickets().getMilestones(repositoryModel));\r
+ Collections.sort(allMilestones, new Comparator<TicketMilestone>() {\r
+ @Override\r
+ public int compare(TicketMilestone o1, TicketMilestone o2) {\r
+ if (o2.isOpen() && !o1.isOpen()) {\r
+ return 1;\r
+ } else if (o1.isOpen() && !o2.isOpen()) {\r
+ return -1;\r
+ }\r
+ return o2.due.compareTo(o1.due);\r
+ }\r
+ });\r
ListDataProvider<TicketMilestone> allMilestonesDp = new ListDataProvider<TicketMilestone>(allMilestones);\r
DataView<TicketMilestone> milestonesList = new DataView<TicketMilestone>("milestoneList", allMilestonesDp) {\r
private static final long serialVersionUID = 1L;\r
item.add(new LinkPanel("milestoneName", null, tm.name, TicketsPage.class, params).setRenderBodyOnly(true));\r
\r
String css;\r
+ String status = tm.status.name();\r
switch (tm.status) {\r
case Open:\r
- css = "aui-lozenge aui-lozenge-subtle";\r
+ if (tm.isOverdue()) {\r
+ css = "aui-lozenge aui-lozenge-subtle aui-lozenge-error";\r
+ status = "overdue";\r
+ } else {\r
+ css = "aui-lozenge aui-lozenge-subtle";\r
+ }\r
break;\r
default:\r
css = "aui-lozenge";\r
break;\r
}\r
- Label stateLabel = new Label("milestoneState", tm.status.name());\r
+ Label stateLabel = new Label("milestoneState", status);\r
WicketUtils.setCssClass(stateLabel, css);\r
item.add(stateLabel);\r
\r