]> source.dussan.org Git - gitblit.git/commitdiff
Create and update milestone pages with rename support
authorJames Moger <james.moger@gitblit.com>
Sat, 26 Apr 2014 03:52:30 +0000 (23:52 -0400)
committerJames Moger <james.moger@gitblit.com>
Thu, 1 May 2014 18:27:28 +0000 (14:27 -0400)
src/main/java/com/gitblit/tickets/ITicketService.java
src/main/java/com/gitblit/tickets/TicketLabel.java
src/main/java/com/gitblit/tickets/TicketMilestone.java
src/main/java/com/gitblit/wicket/GitBlitWebApp.java
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
src/main/java/com/gitblit/wicket/pages/EditMilestonePage.html [new file with mode: 0644]
src/main/java/com/gitblit/wicket/pages/EditMilestonePage.java [new file with mode: 0644]
src/main/java/com/gitblit/wicket/pages/NewMilestonePage.html [new file with mode: 0644]
src/main/java/com/gitblit/wicket/pages/NewMilestonePage.java [new file with mode: 0644]
src/main/java/com/gitblit/wicket/pages/TicketsPage.html
src/main/java/com/gitblit/wicket/pages/TicketsPage.java

index c2f3283ed9978168b0bdc644bf57d9890a543bca..e1a377a692b876d4190dfd1f3d26386e4c7f5eaf 100644 (file)
@@ -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) {
index 686ce88b6809808f532182ecc3e559f15e98ec13..a7f0ebec2ab5780cc43a99bd951ac85d3cd7dbf1 100644 (file)
@@ -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);
        }
index c6b4fcca18f208613d2b84004b5a1c13a5000022..680615a9672d743b7186a61b04b6d156a036185c 100644 (file)
@@ -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();
index c4fdeda586706173c5d124ab8fe689988dbface9..d4c1bc4012dea768e310f503f5dc95ded6c7a27c 100644 (file)
@@ -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");
index aeb2d9ef85be3d84345420cfcdea0af680dd595d..ce4c0b22a8b97e80f37e4d0ec0a1dd75e57523cd 100644 (file)
@@ -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.
\ 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
diff --git a/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.html b/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.html
new file mode 100644 (file)
index 0000000..66b4784
--- /dev/null
@@ -0,0 +1,38 @@
+<!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" /> &nbsp; <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
diff --git a/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.java b/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.java
new file mode 100644 (file)
index 0000000..4407788
--- /dev/null
@@ -0,0 +1,166 @@
+/*\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
diff --git a/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.html b/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.html
new file mode 100644 (file)
index 0000000..1b7e11a
--- /dev/null
@@ -0,0 +1,37 @@
+<!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" /> &nbsp; <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
diff --git a/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.java b/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.java
new file mode 100644 (file)
index 0000000..2c95f01
--- /dev/null
@@ -0,0 +1,123 @@
+/*\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
index 7d13852b79db57e6e2635d131dc851bf424e46ad..e144bea1d227edb1423e5287fef1f4be7d96b95a 100644 (file)
                </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
index ca509e28daa8db1af9981a22f347203b1e2bf72d..984b37544939319f1d60801bd87371a02bdc3beb 100644 (file)
@@ -42,6 +42,7 @@ import com.gitblit.Constants;
 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
@@ -646,7 +647,19 @@ public class TicketsPage extends TicketBasePage {
                };\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
@@ -675,6 +688,12 @@ public class TicketsPage extends TicketBasePage {
                                } 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