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;
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;
* @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!");
}
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();
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) {
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);
}
super(name);
status = Status.Open;
}
+
+ public void setDue(Date due) {
+ this.due = due;
+ }
public int getProgress() {
int total = getTotalTickets();
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;
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;
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");
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.
\ No newline at end of file
+gb.youDoNotHaveClonePermission = You are not permitted to clone this repository.
+gb.newMilestone = new milestone
+gb.editMilestone = edit milestone
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml" \r
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" \r
+ xml:lang="en" \r
+ lang="en"> \r
+\r
+<wicket:extend>\r
+<body onload="document.getElementById('name').focus();">\r
+ \r
+<div class="container">\r
+ <!-- page header -->\r
+ <div class="title" style="font-size: 22px; color: rgb(0, 32, 96);padding: 3px 0px 7px;">\r
+ <span class="project"><wicket:message key="gb.editMilestone"></wicket:message></span>\r
+ </div>\r
+\r
+ <form style="padding-top:5px;" wicket:id="editForm">\r
+ <div class="row">\r
+ <div class="span12">\r
+ <!-- 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.status"></wicket:message><span style="color:red;">*</span></th><td class="edit"><select class="input-large" wicket:id="status"></select></td></tr>\r
+ </table>\r
+ </div>\r
+ </div> \r
+\r
+ <div class="row">\r
+ <div class="span12">\r
+ <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>\r
+ </div>\r
+ </div>\r
+ </form>\r
+</div>\r
+</body>\r
+\r
+</wicket:extend>\r
+</html>
\ No newline at end of file
--- /dev/null
+/*\r
+ * Copyright 2014 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.wicket.pages;\r
+\r
+import java.util.Arrays;\r
+import java.util.Date;\r
+import java.util.List;\r
+\r
+import org.apache.wicket.PageParameters;\r
+import org.apache.wicket.RestartResponseException;\r
+import org.apache.wicket.extensions.markup.html.form.DateTextField;\r
+import org.apache.wicket.markup.html.form.Button;\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
+import org.apache.wicket.model.IModel;\r
+import org.apache.wicket.model.Model;\r
+import org.parboiled.common.StringUtils;\r
+\r
+import com.gitblit.models.RepositoryModel;\r
+import com.gitblit.models.TicketModel;\r
+import com.gitblit.models.TicketModel.Status;\r
+import com.gitblit.models.UserModel;\r
+import com.gitblit.tickets.TicketMilestone;\r
+import com.gitblit.wicket.GitBlitWebSession;\r
+import com.gitblit.wicket.WicketUtils;\r
+\r
+/**\r
+ * Page for creating a new milestone.\r
+ *\r
+ * @author James Moger\r
+ *\r
+ */\r
+public class EditMilestonePage extends RepositoryPage {\r
+\r
+ private final String oldName;\r
+ \r
+ private IModel<String> nameModel;\r
+\r
+ private IModel<Date> dueModel;\r
+ \r
+ private IModel<Status> statusModel;\r
+ \r
+ private IModel<Boolean> notificationModel;\r
+\r
+ public EditMilestonePage(PageParameters params) {\r
+ super(params);\r
+\r
+ RepositoryModel model = getRepositoryModel();\r
+ if (!app().tickets().isAcceptingTicketUpdates(model)) {\r
+ // ticket service is read-only\r
+ throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));\r
+ }\r
+ \r
+ UserModel currentUser = GitBlitWebSession.get().getUser();\r
+ if (currentUser == null) {\r
+ currentUser = UserModel.ANONYMOUS;\r
+ }\r
+\r
+ if (!currentUser.isAuthenticated || !currentUser.canAdmin(model)) {\r
+ // administration prohibited\r
+ throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));\r
+ }\r
+ \r
+ oldName = WicketUtils.getObject(params);\r
+ if (StringUtils.isEmpty(oldName)) {\r
+ // milestone not specified\r
+ throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));\r
+ }\r
+ \r
+ TicketMilestone tm = app().tickets().getMilestone(getRepositoryModel(), oldName);\r
+ if (tm == null) {\r
+ // milestone does not exist\r
+ throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));\r
+ }\r
+\r
+ setStatelessHint(false);\r
+ setOutputMarkupId(true);\r
+\r
+ Form<Void> form = new Form<Void>("editForm") {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ @Override\r
+ protected void onSubmit() {\r
+ \r
+ String name = nameModel.getObject();\r
+ if (StringUtils.isEmpty(name)) {\r
+ return;\r
+ }\r
+ \r
+ Date due = dueModel.getObject();\r
+ Status status = statusModel.getObject();\r
+ boolean rename = !name.equals(oldName);\r
+ boolean notify = notificationModel.getObject();\r
+ \r
+ UserModel currentUser = GitBlitWebSession.get().getUser();\r
+ String createdBy = currentUser.username;\r
+ \r
+ TicketMilestone tm = app().tickets().getMilestone(getRepositoryModel(), oldName);\r
+ tm.setName(name);\r
+ tm.setDue(due);\r
+ tm.status = status;\r
+ \r
+ boolean success = true;\r
+ if (rename) {\r
+ success = app().tickets().renameMilestone(getRepositoryModel(), oldName, name, createdBy, notify);\r
+ }\r
+ \r
+ if (success && app().tickets().updateMilestone(getRepositoryModel(), tm, createdBy)) {\r
+ setResponsePage(TicketsPage.class, WicketUtils.newRepositoryParameter(getRepositoryModel().name));\r
+ } else {\r
+ // TODO error\r
+ }\r
+ }\r
+ };\r
+ add(form);\r
+\r
+ nameModel = Model.of(tm.name);\r
+ dueModel = Model.of(tm.due);\r
+ statusModel = Model.of(tm.status);\r
+ notificationModel = Model.of(true);\r
+ \r
+ form.add(new TextField<String>("name", nameModel));\r
+ form.add(new DateTextField("due", dueModel, "yyyy-MM-dd"));\r
+\r
+ List<Status> statusChoices = Arrays.asList(Status.Open, Status.Closed);\r
+ form.add(new DropDownChoice<TicketModel.Status>("status", statusModel, statusChoices));\r
+\r
+ form.add(new Button("save"));\r
+ Button cancel = new Button("cancel") {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ @Override\r
+ public void onSubmit() {\r
+ setResponsePage(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));\r
+ }\r
+ };\r
+ cancel.setDefaultFormProcessing(false);\r
+ form.add(cancel);\r
+\r
+ }\r
+\r
+ @Override\r
+ protected String getPageName() {\r
+ return getString("gb.editMilestone");\r
+ }\r
+\r
+ @Override\r
+ protected Class<? extends BasePage> getRepoNavPageClass() {\r
+ return TicketsPage.class;\r
+ }\r
+}\r
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml" \r
+ xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" \r
+ xml:lang="en" \r
+ lang="en"> \r
+\r
+<wicket:extend>\r
+<body onload="document.getElementById('name').focus();">\r
+ \r
+<div class="container">\r
+ <!-- page header -->\r
+ <div class="title" style="font-size: 22px; color: rgb(0, 32, 96);padding: 3px 0px 7px;">\r
+ <span class="project"><wicket:message key="gb.newMilestone"></wicket:message></span>\r
+ </div>\r
+\r
+ <form style="padding-top:5px;" wicket:id="editForm">\r
+ <div class="row">\r
+ <div class="span12">\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
+ </table>\r
+ </div>\r
+ </div> \r
+\r
+ <div class="row">\r
+ <div class="span12">\r
+ <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>\r
+ </div>\r
+ </div>\r
+ </form>\r
+</div>\r
+</body>\r
+\r
+</wicket:extend>\r
+</html>
\ No newline at end of file
--- /dev/null
+/*\r
+ * Copyright 2014 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.wicket.pages;\r
+\r
+import java.util.Date;\r
+\r
+import org.apache.wicket.PageParameters;\r
+import org.apache.wicket.RestartResponseException;\r
+import org.apache.wicket.extensions.markup.html.form.DateTextField;\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
+import org.apache.wicket.model.IModel;\r
+import org.apache.wicket.model.Model;\r
+\r
+import com.gitblit.models.RepositoryModel;\r
+import com.gitblit.models.UserModel;\r
+import com.gitblit.tickets.TicketMilestone;\r
+import com.gitblit.wicket.GitBlitWebSession;\r
+import com.gitblit.wicket.WicketUtils;\r
+\r
+/**\r
+ * Page for creating a new milestone.\r
+ *\r
+ * @author James Moger\r
+ *\r
+ */\r
+public class NewMilestonePage extends RepositoryPage {\r
+\r
+ private IModel<String> nameModel;\r
+\r
+ private IModel<Date> dueModel;\r
+ \r
+ public NewMilestonePage(PageParameters params) {\r
+ super(params);\r
+\r
+ RepositoryModel model = getRepositoryModel();\r
+ if (!app().tickets().isAcceptingTicketUpdates(model)) {\r
+ // ticket service is read-only\r
+ throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));\r
+ }\r
+ \r
+ UserModel currentUser = GitBlitWebSession.get().getUser();\r
+ if (currentUser == null) {\r
+ currentUser = UserModel.ANONYMOUS;\r
+ }\r
+\r
+ if (!currentUser.isAuthenticated || !currentUser.canAdmin(model)) {\r
+ // administration prohibited\r
+ throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));\r
+ }\r
+\r
+ setStatelessHint(false);\r
+ setOutputMarkupId(true);\r
+\r
+ Form<Void> form = new Form<Void>("editForm") {\r
+\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ @Override\r
+ protected void onSubmit() {\r
+ \r
+ String name = nameModel.getObject();\r
+ Date due = dueModel.getObject();\r
+\r
+ UserModel currentUser = GitBlitWebSession.get().getUser();\r
+ String createdBy = currentUser.username;\r
+ \r
+ TicketMilestone milestone = app().tickets().createMilestone(getRepositoryModel(), name, createdBy);\r
+ if (milestone != null) {\r
+ milestone.due = due;\r
+ app().tickets().updateMilestone(getRepositoryModel(), milestone, createdBy);\r
+ throw new RestartResponseException(TicketsPage.class, WicketUtils.newRepositoryParameter(getRepositoryModel().name));\r
+ } else {\r
+ // TODO error\r
+ }\r
+ }\r
+ };\r
+ add(form);\r
+\r
+ nameModel = Model.of("");\r
+ dueModel = Model.of(new Date());\r
+ \r
+ form.add(new TextField<String>("name", nameModel));\r
+ form.add(new DateTextField("due", dueModel, "yyyy-MM-dd"));\r
+\r
+ form.add(new Button("create"));\r
+ Button cancel = new Button("cancel") {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ @Override\r
+ public void onSubmit() {\r
+ setResponsePage(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));\r
+ }\r
+ };\r
+ cancel.setDefaultFormProcessing(false);\r
+ form.add(cancel);\r
+\r
+ }\r
+\r
+ @Override\r
+ protected String getPageName() {\r
+ return getString("gb.newMilestone");\r
+ }\r
+\r
+ @Override\r
+ protected Class<? extends BasePage> getRepoNavPageClass() {\r
+ return TicketsPage.class;\r
+ }\r
+}\r
</div>\r
</div>\r
<div class="tab-pane" id="milestones">\r
+ <div class="row">\r
+ <span class="span12" style="padding-bottom:10px;" wicket:id="newMilestone"></span>\r
+ </div>\r
<div class="row">\r
<div class="span9" wicket:id="milestoneList" style="padding-bottom: 10px;">\r
<h3><span wicket:id="milestoneName"></span> <small><span wicket:id="milestoneState"></span></small></h3>\r
- <i style="color:#888;"class="fa fa-calendar"></i> <span wicket:id="milestoneDue"></span>\r
+ <i style="color:#888;"class="fa fa-calendar"></i> <span wicket:id="milestoneDue"></span> <span wicket:id="editMilestone"></span>\r
</div>\r
</div>\r
</div>\r
import com.gitblit.Constants.AccessPermission;\r
import com.gitblit.Keys;\r
import com.gitblit.models.RegistrantAccessPermission;\r
+import com.gitblit.models.RepositoryModel;\r
import com.gitblit.models.TicketModel;\r
import com.gitblit.models.TicketModel.Status;\r
import com.gitblit.models.UserModel;\r
};\r
add(ticketsView);\r
\r
- List<TicketMilestone> allMilestones = app().tickets().getMilestones(getRepositoryModel());\r
+ // new milestone link\r
+ RepositoryModel repositoryModel = getRepositoryModel();\r
+ final boolean acceptingUpdates = app().tickets().isAcceptingTicketUpdates(repositoryModel)\r
+ && user != null && user.canAdmin(getRepositoryModel());\r
+ if (acceptingUpdates) {\r
+ add(new LinkPanel("newMilestone", null, getString("gb.newMilestone"),\r
+ NewMilestonePage.class, WicketUtils.newRepositoryParameter(repositoryName)));\r
+ } else {\r
+ add(new Label("newMilestone").setVisible(false));\r
+ }\r
+ \r
+ // milestones list\r
+ List<TicketMilestone> allMilestones = app().tickets().getMilestones(repositoryModel);\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
} else {\r
item.add(WicketUtils.createDatestampLabel("milestoneDue", tm.due, getTimeZone(), getTimeUtils()));\r
}\r
+ if (acceptingUpdates) {\r
+ item.add(new LinkPanel("editMilestone", null, getString("gb.edit"), EditMilestonePage.class,\r
+ WicketUtils.newObjectParameter(repositoryName, tm.name)));\r
+ } else {\r
+ item.add(new Label("editMilestone").setVisible(false));\r
+ }\r
}\r
};\r
add(milestonesList);\r