@@ -651,11 +651,12 @@ public abstract class ITicketService { | |||
* @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!"); | |||
} | |||
@@ -680,11 +681,11 @@ public abstract class ITicketService { | |||
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(); | |||
} | |||
@@ -709,6 +710,21 @@ public abstract class ITicketService { | |||
* @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!"); | |||
} | |||
@@ -722,14 +738,18 @@ public abstract class ITicketService { | |||
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); |
@@ -37,7 +37,15 @@ public class TicketMilestone extends TicketLabel { | |||
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; | |||
} |
@@ -673,4 +673,6 @@ gb.mergeToDescription = default integration branch for merging ticket patchsets | |||
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 | |||
gb.editMilestone = edit milestone | |||
gb.notifyChangedOpenTickets = send notification for changed open tickets | |||
gb.overdue = overdue |
@@ -19,8 +19,9 @@ | |||
<!-- Edit Milestone Table --> | |||
<table class="ticket"> | |||
<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> | |||
<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> | |||
<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> | |||
<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> | |||
<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> | |||
</table> | |||
</div> | |||
</div> |
@@ -24,7 +24,9 @@ import org.apache.wicket.RestartResponseException; | |||
import org.apache.wicket.ajax.AjaxRequestTarget; | |||
import org.apache.wicket.ajax.markup.html.form.AjaxButton; | |||
import org.apache.wicket.extensions.markup.html.form.DateTextField; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import org.apache.wicket.markup.html.form.Button; | |||
import org.apache.wicket.markup.html.form.CheckBox; | |||
import org.apache.wicket.markup.html.form.DropDownChoice; | |||
import org.apache.wicket.markup.html.form.Form; | |||
import org.apache.wicket.markup.html.form.TextField; | |||
@@ -102,6 +104,8 @@ public class EditMilestonePage extends RepositoryPage { | |||
form.add(new TextField<String>("name", nameModel)); | |||
form.add(new DateTextField("due", dueModel, "yyyy-MM-dd")); | |||
form.add(new Label("dueFormat", "yyyy-MM-dd")); | |||
form.add(new CheckBox("notify", notificationModel)); | |||
List<Status> statusChoices = Arrays.asList(Status.Open, Status.Closed); | |||
form.add(new DropDownChoice<TicketModel.Status>("status", statusModel, statusChoices)); | |||
@@ -160,8 +164,9 @@ public class EditMilestonePage extends RepositoryPage { | |||
public void onSubmit() { | |||
UserModel currentUser = GitBlitWebSession.get().getUser(); | |||
String createdBy = currentUser.username; | |||
boolean notify = notificationModel.getObject(); | |||
if (app().tickets().deleteMilestone(getRepositoryModel(), oldName, createdBy)) { | |||
if (app().tickets().deleteMilestone(getRepositoryModel(), oldName, createdBy, notify)) { | |||
setResponsePage(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName)); | |||
} else { | |||
// TODO error processing |
@@ -19,7 +19,7 @@ | |||
<!-- New Milestone Table --> | |||
<table class="ticket"> | |||
<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> | |||
<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> | |||
<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> | |||
</table> | |||
</div> | |||
</div> |
@@ -22,6 +22,7 @@ import org.apache.wicket.RestartResponseException; | |||
import org.apache.wicket.ajax.AjaxRequestTarget; | |||
import org.apache.wicket.ajax.markup.html.form.AjaxButton; | |||
import org.apache.wicket.extensions.markup.html.form.DateTextField; | |||
import org.apache.wicket.markup.html.basic.Label; | |||
import org.apache.wicket.markup.html.form.Button; | |||
import org.apache.wicket.markup.html.form.Form; | |||
import org.apache.wicket.markup.html.form.TextField; | |||
@@ -78,6 +79,7 @@ public class NewMilestonePage extends RepositoryPage { | |||
form.add(new TextField<String>("name", nameModel)); | |||
form.add(new DateTextField("due", dueModel, "yyyy-MM-dd")); | |||
form.add(new Label("dueFormat", "yyyy-MM-dd")); | |||
form.add(new AjaxButton("create") { | |||
@@ -87,6 +89,13 @@ public class NewMilestonePage extends RepositoryPage { | |||
protected void onSubmit(AjaxRequestTarget target, Form<?> form) { | |||
String name = nameModel.getObject(); | |||
if (StringUtils.isEmpty(name)) { | |||
// invalid name | |||
return; | |||
} | |||
TicketMilestone milestone = app().tickets().getMilestone(getRepositoryModel(), name); | |||
if (milestone != null) { | |||
// milestone already exists | |||
return; | |||
} | |||
@@ -95,7 +104,7 @@ public class NewMilestonePage extends RepositoryPage { | |||
UserModel currentUser = GitBlitWebSession.get().getUser(); | |||
String createdBy = currentUser.username; | |||
TicketMilestone milestone = app().tickets().createMilestone(getRepositoryModel(), name, createdBy); | |||
milestone = app().tickets().createMilestone(getRepositoryModel(), name, createdBy); | |||
if (milestone != null) { | |||
milestone.due = due; | |||
app().tickets().updateMilestone(getRepositoryModel(), milestone, createdBy); |
@@ -20,6 +20,7 @@ import java.text.MessageFormat; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.Comparator; | |||
import java.util.List; | |||
import java.util.Set; | |||
import java.util.TreeSet; | |||
@@ -657,9 +658,20 @@ public class TicketsPage extends TicketBasePage { | |||
} else { | |||
add(new Label("newMilestone").setVisible(false)); | |||
} | |||
// milestones list | |||
List<TicketMilestone> allMilestones = app().tickets().getMilestones(repositoryModel); | |||
List<TicketMilestone> allMilestones = new ArrayList<TicketMilestone>(app().tickets().getMilestones(repositoryModel)); | |||
Collections.sort(allMilestones, new Comparator<TicketMilestone>() { | |||
@Override | |||
public int compare(TicketMilestone o1, TicketMilestone o2) { | |||
if (o2.isOpen() && !o1.isOpen()) { | |||
return 1; | |||
} else if (o1.isOpen() && !o2.isOpen()) { | |||
return -1; | |||
} | |||
return o2.due.compareTo(o1.due); | |||
} | |||
}); | |||
ListDataProvider<TicketMilestone> allMilestonesDp = new ListDataProvider<TicketMilestone>(allMilestones); | |||
DataView<TicketMilestone> milestonesList = new DataView<TicketMilestone>("milestoneList", allMilestonesDp) { | |||
private static final long serialVersionUID = 1L; | |||
@@ -671,15 +683,21 @@ public class TicketsPage extends TicketBasePage { | |||
item.add(new LinkPanel("milestoneName", null, tm.name, TicketsPage.class, params).setRenderBodyOnly(true)); | |||
String css; | |||
String status = tm.status.name(); | |||
switch (tm.status) { | |||
case Open: | |||
css = "aui-lozenge aui-lozenge-subtle"; | |||
if (tm.isOverdue()) { | |||
css = "aui-lozenge aui-lozenge-subtle aui-lozenge-error"; | |||
status = "overdue"; | |||
} else { | |||
css = "aui-lozenge aui-lozenge-subtle"; | |||
} | |||
break; | |||
default: | |||
css = "aui-lozenge"; | |||
break; | |||
} | |||
Label stateLabel = new Label("milestoneState", tm.status.name()); | |||
Label stateLabel = new Label("milestoneState", status); | |||
WicketUtils.setCssClass(stateLabel, css); | |||
item.add(stateLabel); | |||