src/main/java/com/gitblit/tickets/ITicketService.java
@@ -49,6 +49,7 @@ 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 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 @@ * @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 @@ 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 @@ Change change = new Change(createdBy); change.setField(Field.milestone, newName); TicketModel ticket = updateTicket(repository, qr.number, change); if (notify && ticket.isOpen()) { notifier.queueMailing(ticket); } } if (notify) { notifier.sendAll(); } return true; } catch (IOException e) { src/main/java/com/gitblit/tickets/TicketLabel.java
@@ -30,14 +30,17 @@ 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); } src/main/java/com/gitblit/tickets/TicketMilestone.java
@@ -37,6 +37,10 @@ super(name); status = Status.Open; } public void setDue(Date due) { this.due = due; } public int getProgress() { int total = getTotalTickets(); src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -51,6 +51,7 @@ 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.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 @@ 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"); src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -672,3 +672,5 @@ 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 src/main/java/com/gitblit/wicket/pages/EditMilestonePage.html
New file @@ -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> src/main/java/com/gitblit/wicket/pages/EditMilestonePage.java
New file @@ -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; } } src/main/java/com/gitblit/wicket/pages/NewMilestonePage.html
New file @@ -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> src/main/java/com/gitblit/wicket/pages/NewMilestonePage.java
New file @@ -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; } } src/main/java/com/gitblit/wicket/pages/TicketsPage.html
@@ -139,9 +139,12 @@ </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> src/main/java/com/gitblit/wicket/pages/TicketsPage.java
@@ -42,6 +42,7 @@ 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 @@ }; 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 @@ } 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);