@@ -49,6 +49,7 @@ import com.gitblit.models.TicketModel.Field; | |||
import com.gitblit.models.TicketModel.Patchset; | |||
import com.gitblit.models.TicketModel.Status; | |||
import com.gitblit.tickets.TicketIndexer.Lucene; | |||
import com.gitblit.utils.DeepCopier; | |||
import com.gitblit.utils.DiffUtils; | |||
import com.gitblit.utils.DiffUtils.DiffStat; | |||
import com.gitblit.utils.StringUtils; | |||
@@ -556,9 +557,10 @@ public abstract class ITicketService { | |||
public TicketMilestone getMilestone(RepositoryModel repository, String milestone) { | |||
for (TicketMilestone ms : getMilestones(repository)) { | |||
if (ms.name.equalsIgnoreCase(milestone)) { | |||
TicketMilestone tm = DeepCopier.copy(ms); | |||
String q = QueryBuilder.q(Lucene.rid.matches(repository.getRID())).and(Lucene.milestone.matches(milestone)).build(); | |||
ms.tickets = indexer.queryFor(q, 1, 0, Lucene.number.name(), true); | |||
return ms; | |||
tm.tickets = indexer.queryFor(q, 1, 0, Lucene.number.name(), true); | |||
return tm; | |||
} | |||
} | |||
return null; | |||
@@ -639,6 +641,21 @@ public abstract class ITicketService { | |||
* @since 1.4.0 | |||
*/ | |||
public synchronized boolean renameMilestone(RepositoryModel repository, String oldName, String newName, String createdBy) { | |||
return renameMilestone(repository, oldName, newName, createdBy, true); | |||
} | |||
/** | |||
* Renames a milestone. | |||
* | |||
* @param repository | |||
* @param oldName | |||
* @param newName | |||
* @param createdBy | |||
* @param send ticket notifications | |||
* @return true if successful | |||
* @since 1.6.0 | |||
*/ | |||
public synchronized boolean renameMilestone(RepositoryModel repository, String oldName, String newName, String createdBy, boolean notify) { | |||
if (StringUtils.isEmpty(newName)) { | |||
throw new IllegalArgumentException("new milestone can not be empty!"); | |||
} | |||
@@ -651,7 +668,7 @@ public abstract class ITicketService { | |||
config.setString(MILESTONE, newName, STATUS, milestone.status.name()); | |||
config.setString(MILESTONE, newName, COLOR, milestone.color); | |||
if (milestone.due != null) { | |||
config.setString(MILESTONE, milestone.name, DUE, | |||
config.setString(MILESTONE, newName, DUE, | |||
new SimpleDateFormat(DUE_DATE_PATTERN).format(milestone.due)); | |||
} | |||
config.save(); | |||
@@ -663,9 +680,13 @@ public abstract class ITicketService { | |||
Change change = new Change(createdBy); | |||
change.setField(Field.milestone, newName); | |||
TicketModel ticket = updateTicket(repository, qr.number, change); | |||
notifier.queueMailing(ticket); | |||
if (notify && ticket.isOpen()) { | |||
notifier.queueMailing(ticket); | |||
} | |||
} | |||
if (notify) { | |||
notifier.sendAll(); | |||
} | |||
notifier.sendAll(); | |||
return true; | |||
} catch (IOException e) { |
@@ -30,14 +30,17 @@ public class TicketLabel implements Serializable { | |||
private static final long serialVersionUID = 1L; | |||
public final String name; | |||
public String name; | |||
public String color; | |||
public List<QueryResult> tickets; | |||
public TicketLabel(String name) { | |||
setName(name); | |||
} | |||
public void setName(String name) { | |||
this.name = name; | |||
this.color = StringUtils.getColor(name); | |||
} |
@@ -37,6 +37,10 @@ public class TicketMilestone extends TicketLabel { | |||
super(name); | |||
status = Status.Open; | |||
} | |||
public void setDue(Date due) { | |||
this.due = due; | |||
} | |||
public int getProgress() { | |||
int total = getTotalTickets(); |
@@ -51,6 +51,7 @@ import com.gitblit.wicket.pages.CommitPage; | |||
import com.gitblit.wicket.pages.ComparePage; | |||
import com.gitblit.wicket.pages.DocPage; | |||
import com.gitblit.wicket.pages.DocsPage; | |||
import com.gitblit.wicket.pages.EditMilestonePage; | |||
import com.gitblit.wicket.pages.EditTicketPage; | |||
import com.gitblit.wicket.pages.ExportTicketPage; | |||
import com.gitblit.wicket.pages.FederationRegistrationPage; | |||
@@ -63,6 +64,7 @@ import com.gitblit.wicket.pages.LogoutPage; | |||
import com.gitblit.wicket.pages.LuceneSearchPage; | |||
import com.gitblit.wicket.pages.MetricsPage; | |||
import com.gitblit.wicket.pages.MyDashboardPage; | |||
import com.gitblit.wicket.pages.NewMilestonePage; | |||
import com.gitblit.wicket.pages.NewTicketPage; | |||
import com.gitblit.wicket.pages.OverviewPage; | |||
import com.gitblit.wicket.pages.PatchPage; | |||
@@ -187,6 +189,8 @@ public class GitBlitWebApp extends WebApplication { | |||
mount("/tickets/new", NewTicketPage.class, "r"); | |||
mount("/tickets/edit", EditTicketPage.class, "r", "h"); | |||
mount("/tickets/export", ExportTicketPage.class, "r", "h"); | |||
mount("/milestones/new", NewMilestonePage.class, "r"); | |||
mount("/milestones/edit", EditMilestonePage.class, "r", "h"); | |||
// setup the markup document urls | |||
mount("/docs", DocsPage.class, "r"); |
@@ -671,4 +671,6 @@ gb.serverDoesNotAcceptPatchsets = This server does not accept patchsets. | |||
gb.ticketIsClosed = This ticket is closed. | |||
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.youDoNotHaveClonePermission = You are not permitted to clone this repository. | |||
gb.newMilestone = new milestone | |||
gb.editMilestone = edit milestone |
@@ -0,0 +1,38 @@ | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" | |||
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" | |||
xml:lang="en" | |||
lang="en"> | |||
<wicket:extend> | |||
<body onload="document.getElementById('name').focus();"> | |||
<div class="container"> | |||
<!-- page header --> | |||
<div class="title" style="font-size: 22px; color: rgb(0, 32, 96);padding: 3px 0px 7px;"> | |||
<span class="project"><wicket:message key="gb.editMilestone"></wicket:message></span> | |||
</div> | |||
<form style="padding-top:5px;" wicket:id="editForm"> | |||
<div class="row"> | |||
<div class="span12"> | |||
<!-- 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.status"></wicket:message><span style="color:red;">*</span></th><td class="edit"><select class="input-large" wicket:id="status"></select></td></tr> | |||
</table> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="span12"> | |||
<div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /></div> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
</body> | |||
</wicket:extend> | |||
</html> |
@@ -0,0 +1,166 @@ | |||
/* | |||
* Copyright 2014 gitblit.com. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.gitblit.wicket.pages; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import java.util.List; | |||
import org.apache.wicket.PageParameters; | |||
import org.apache.wicket.RestartResponseException; | |||
import org.apache.wicket.extensions.markup.html.form.DateTextField; | |||
import org.apache.wicket.markup.html.form.Button; | |||
import org.apache.wicket.markup.html.form.DropDownChoice; | |||
import org.apache.wicket.markup.html.form.Form; | |||
import org.apache.wicket.markup.html.form.TextField; | |||
import org.apache.wicket.model.IModel; | |||
import org.apache.wicket.model.Model; | |||
import org.parboiled.common.StringUtils; | |||
import com.gitblit.models.RepositoryModel; | |||
import com.gitblit.models.TicketModel; | |||
import com.gitblit.models.TicketModel.Status; | |||
import com.gitblit.models.UserModel; | |||
import com.gitblit.tickets.TicketMilestone; | |||
import com.gitblit.wicket.GitBlitWebSession; | |||
import com.gitblit.wicket.WicketUtils; | |||
/** | |||
* Page for creating a new milestone. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
public class EditMilestonePage extends RepositoryPage { | |||
private final String oldName; | |||
private IModel<String> nameModel; | |||
private IModel<Date> dueModel; | |||
private IModel<Status> statusModel; | |||
private IModel<Boolean> notificationModel; | |||
public EditMilestonePage(PageParameters params) { | |||
super(params); | |||
RepositoryModel model = getRepositoryModel(); | |||
if (!app().tickets().isAcceptingTicketUpdates(model)) { | |||
// ticket service is read-only | |||
throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName)); | |||
} | |||
UserModel currentUser = GitBlitWebSession.get().getUser(); | |||
if (currentUser == null) { | |||
currentUser = UserModel.ANONYMOUS; | |||
} | |||
if (!currentUser.isAuthenticated || !currentUser.canAdmin(model)) { | |||
// administration prohibited | |||
throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName)); | |||
} | |||
oldName = WicketUtils.getObject(params); | |||
if (StringUtils.isEmpty(oldName)) { | |||
// milestone not specified | |||
throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName)); | |||
} | |||
TicketMilestone tm = app().tickets().getMilestone(getRepositoryModel(), oldName); | |||
if (tm == null) { | |||
// milestone does not exist | |||
throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName)); | |||
} | |||
setStatelessHint(false); | |||
setOutputMarkupId(true); | |||
Form<Void> form = new Form<Void>("editForm") { | |||
private static final long serialVersionUID = 1L; | |||
@Override | |||
protected void onSubmit() { | |||
String name = nameModel.getObject(); | |||
if (StringUtils.isEmpty(name)) { | |||
return; | |||
} | |||
Date due = dueModel.getObject(); | |||
Status status = statusModel.getObject(); | |||
boolean rename = !name.equals(oldName); | |||
boolean notify = notificationModel.getObject(); | |||
UserModel currentUser = GitBlitWebSession.get().getUser(); | |||
String createdBy = currentUser.username; | |||
TicketMilestone tm = app().tickets().getMilestone(getRepositoryModel(), oldName); | |||
tm.setName(name); | |||
tm.setDue(due); | |||
tm.status = status; | |||
boolean success = true; | |||
if (rename) { | |||
success = app().tickets().renameMilestone(getRepositoryModel(), oldName, name, createdBy, notify); | |||
} | |||
if (success && app().tickets().updateMilestone(getRepositoryModel(), tm, createdBy)) { | |||
setResponsePage(TicketsPage.class, WicketUtils.newRepositoryParameter(getRepositoryModel().name)); | |||
} else { | |||
// TODO error | |||
} | |||
} | |||
}; | |||
add(form); | |||
nameModel = Model.of(tm.name); | |||
dueModel = Model.of(tm.due); | |||
statusModel = Model.of(tm.status); | |||
notificationModel = Model.of(true); | |||
form.add(new TextField<String>("name", nameModel)); | |||
form.add(new DateTextField("due", dueModel, "yyyy-MM-dd")); | |||
List<Status> statusChoices = Arrays.asList(Status.Open, Status.Closed); | |||
form.add(new DropDownChoice<TicketModel.Status>("status", statusModel, statusChoices)); | |||
form.add(new Button("save")); | |||
Button cancel = new Button("cancel") { | |||
private static final long serialVersionUID = 1L; | |||
@Override | |||
public void onSubmit() { | |||
setResponsePage(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName)); | |||
} | |||
}; | |||
cancel.setDefaultFormProcessing(false); | |||
form.add(cancel); | |||
} | |||
@Override | |||
protected String getPageName() { | |||
return getString("gb.editMilestone"); | |||
} | |||
@Override | |||
protected Class<? extends BasePage> getRepoNavPageClass() { | |||
return TicketsPage.class; | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" | |||
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" | |||
xml:lang="en" | |||
lang="en"> | |||
<wicket:extend> | |||
<body onload="document.getElementById('name').focus();"> | |||
<div class="container"> | |||
<!-- page header --> | |||
<div class="title" style="font-size: 22px; color: rgb(0, 32, 96);padding: 3px 0px 7px;"> | |||
<span class="project"><wicket:message key="gb.newMilestone"></wicket:message></span> | |||
</div> | |||
<form style="padding-top:5px;" wicket:id="editForm"> | |||
<div class="row"> | |||
<div class="span12"> | |||
<!-- 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> | |||
</table> | |||
</div> | |||
</div> | |||
<div class="row"> | |||
<div class="span12"> | |||
<div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Create" wicket:message="value:gb.create" wicket:id="create" /> <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /></div> | |||
</div> | |||
</div> | |||
</form> | |||
</div> | |||
</body> | |||
</wicket:extend> | |||
</html> |
@@ -0,0 +1,123 @@ | |||
/* | |||
* Copyright 2014 gitblit.com. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.gitblit.wicket.pages; | |||
import java.util.Date; | |||
import org.apache.wicket.PageParameters; | |||
import org.apache.wicket.RestartResponseException; | |||
import org.apache.wicket.extensions.markup.html.form.DateTextField; | |||
import org.apache.wicket.markup.html.form.Button; | |||
import org.apache.wicket.markup.html.form.Form; | |||
import org.apache.wicket.markup.html.form.TextField; | |||
import org.apache.wicket.model.IModel; | |||
import org.apache.wicket.model.Model; | |||
import com.gitblit.models.RepositoryModel; | |||
import com.gitblit.models.UserModel; | |||
import com.gitblit.tickets.TicketMilestone; | |||
import com.gitblit.wicket.GitBlitWebSession; | |||
import com.gitblit.wicket.WicketUtils; | |||
/** | |||
* Page for creating a new milestone. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
public class NewMilestonePage extends RepositoryPage { | |||
private IModel<String> nameModel; | |||
private IModel<Date> dueModel; | |||
public NewMilestonePage(PageParameters params) { | |||
super(params); | |||
RepositoryModel model = getRepositoryModel(); | |||
if (!app().tickets().isAcceptingTicketUpdates(model)) { | |||
// ticket service is read-only | |||
throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName)); | |||
} | |||
UserModel currentUser = GitBlitWebSession.get().getUser(); | |||
if (currentUser == null) { | |||
currentUser = UserModel.ANONYMOUS; | |||
} | |||
if (!currentUser.isAuthenticated || !currentUser.canAdmin(model)) { | |||
// administration prohibited | |||
throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName)); | |||
} | |||
setStatelessHint(false); | |||
setOutputMarkupId(true); | |||
Form<Void> form = new Form<Void>("editForm") { | |||
private static final long serialVersionUID = 1L; | |||
@Override | |||
protected void onSubmit() { | |||
String name = nameModel.getObject(); | |||
Date due = dueModel.getObject(); | |||
UserModel currentUser = GitBlitWebSession.get().getUser(); | |||
String createdBy = currentUser.username; | |||
TicketMilestone milestone = app().tickets().createMilestone(getRepositoryModel(), name, createdBy); | |||
if (milestone != null) { | |||
milestone.due = due; | |||
app().tickets().updateMilestone(getRepositoryModel(), milestone, createdBy); | |||
throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(getRepositoryModel().name)); | |||
} else { | |||
// TODO error | |||
} | |||
} | |||
}; | |||
add(form); | |||
nameModel = Model.of(""); | |||
dueModel = Model.of(new Date()); | |||
form.add(new TextField<String>("name", nameModel)); | |||
form.add(new DateTextField("due", dueModel, "yyyy-MM-dd")); | |||
form.add(new Button("create")); | |||
Button cancel = new Button("cancel") { | |||
private static final long serialVersionUID = 1L; | |||
@Override | |||
public void onSubmit() { | |||
setResponsePage(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName)); | |||
} | |||
}; | |||
cancel.setDefaultFormProcessing(false); | |||
form.add(cancel); | |||
} | |||
@Override | |||
protected String getPageName() { | |||
return getString("gb.newMilestone"); | |||
} | |||
@Override | |||
protected Class<? extends BasePage> getRepoNavPageClass() { | |||
return TicketsPage.class; | |||
} | |||
} |
@@ -138,10 +138,13 @@ | |||
</div> | |||
</div> | |||
<div class="tab-pane" id="milestones"> | |||
<div class="row"> | |||
<span class="span12" style="padding-bottom:10px;" wicket:id="newMilestone"></span> | |||
</div> | |||
<div class="row"> | |||
<div class="span9" wicket:id="milestoneList" style="padding-bottom: 10px;"> | |||
<h3><span wicket:id="milestoneName"></span> <small><span wicket:id="milestoneState"></span></small></h3> | |||
<i style="color:#888;"class="fa fa-calendar"></i> <span wicket:id="milestoneDue"></span> | |||
<i style="color:#888;"class="fa fa-calendar"></i> <span wicket:id="milestoneDue"></span> <span wicket:id="editMilestone"></span> | |||
</div> | |||
</div> | |||
</div> |
@@ -42,6 +42,7 @@ import com.gitblit.Constants; | |||
import com.gitblit.Constants.AccessPermission; | |||
import com.gitblit.Keys; | |||
import com.gitblit.models.RegistrantAccessPermission; | |||
import com.gitblit.models.RepositoryModel; | |||
import com.gitblit.models.TicketModel; | |||
import com.gitblit.models.TicketModel.Status; | |||
import com.gitblit.models.UserModel; | |||
@@ -646,7 +647,19 @@ public class TicketsPage extends TicketBasePage { | |||
}; | |||
add(ticketsView); | |||
List<TicketMilestone> allMilestones = app().tickets().getMilestones(getRepositoryModel()); | |||
// new milestone link | |||
RepositoryModel repositoryModel = getRepositoryModel(); | |||
final boolean acceptingUpdates = app().tickets().isAcceptingTicketUpdates(repositoryModel) | |||
&& user != null && user.canAdmin(getRepositoryModel()); | |||
if (acceptingUpdates) { | |||
add(new LinkPanel("newMilestone", null, getString("gb.newMilestone"), | |||
NewMilestonePage.class, WicketUtils.newRepositoryParameter(repositoryName))); | |||
} else { | |||
add(new Label("newMilestone").setVisible(false)); | |||
} | |||
// milestones list | |||
List<TicketMilestone> allMilestones = app().tickets().getMilestones(repositoryModel); | |||
ListDataProvider<TicketMilestone> allMilestonesDp = new ListDataProvider<TicketMilestone>(allMilestones); | |||
DataView<TicketMilestone> milestonesList = new DataView<TicketMilestone>("milestoneList", allMilestonesDp) { | |||
private static final long serialVersionUID = 1L; | |||
@@ -675,6 +688,12 @@ public class TicketsPage extends TicketBasePage { | |||
} else { | |||
item.add(WicketUtils.createDatestampLabel("milestoneDue", tm.due, getTimeZone(), getTimeUtils())); | |||
} | |||
if (acceptingUpdates) { | |||
item.add(new LinkPanel("editMilestone", null, getString("gb.edit"), EditMilestonePage.class, | |||
WicketUtils.newObjectParameter(repositoryName, tm.name))); | |||
} else { | |||
item.add(new Label("editMilestone").setVisible(false)); | |||
} | |||
} | |||
}; | |||
add(milestonesList); |