diff options
author | James Moger <james.moger@gitblit.com> | 2014-05-01 12:55:57 -0600 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2014-05-01 12:55:57 -0600 |
commit | c9de84169e407fbf1963e7ad0e1f3f3e57cfae24 (patch) | |
tree | 8e4abf87dc4f03d1d4c0bcccf08c630bb1c3daed | |
parent | 98cee046a95b92b7608fb4bb4371f4af55f1bf38 (diff) | |
parent | 8d2caa7e81fcd995f0a5c07fa4454ae2f8e86e6e (diff) | |
download | gitblit-c9de84169e407fbf1963e7ad0e1f3f3e57cfae24.tar.gz gitblit-c9de84169e407fbf1963e7ad0e1f3f3e57cfae24.zip |
Merged #17 "CRUD Milestone Pages"
12 files changed, 631 insertions, 22 deletions
diff --git a/src/main/java/com/gitblit/tickets/ITicketService.java b/src/main/java/com/gitblit/tickets/ITicketService.java index c2f3283e..3261ca96 100644 --- a/src/main/java/com/gitblit/tickets/ITicketService.java +++ b/src/main/java/com/gitblit/tickets/ITicketService.java @@ -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,22 @@ 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 notifyOpenTickets + * @return true if successful + * @since 1.6.0 + */ + 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!"); } @@ -651,7 +669,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 +681,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 (notifyOpenTickets && ticket.isOpen()) { + notifier.queueMailing(ticket); + } + } + if (notifyOpenTickets) { + notifier.sendAll(); } - notifier.sendAll(); return true; } catch (IOException e) { @@ -688,11 +710,27 @@ 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!"); } Repository db = null; try { + TicketMilestone tm = getMilestone(repository, milestone); db = repositoryManager.getRepository(repository.name); StoredConfig config = db.getConfig(); config.unsetSection(MILESTONE, milestone); @@ -700,6 +738,18 @@ public abstract class ITicketService { milestonesCache.remove(repository.name); + TicketNotifier notifier = createNotifier(); + for (QueryResult qr : tm.tickets) { + 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); diff --git a/src/main/java/com/gitblit/tickets/QueryResult.java b/src/main/java/com/gitblit/tickets/QueryResult.java index 9f5d3a55..7a2b1abe 100644 --- a/src/main/java/com/gitblit/tickets/QueryResult.java +++ b/src/main/java/com/gitblit/tickets/QueryResult.java @@ -74,6 +74,14 @@ public class QueryResult implements Serializable { return type != null && Type.Proposal == type; } + public boolean isOpen() { + return !status.isClosed(); + } + + public boolean isClosed() { + return status.isClosed(); + } + public boolean isMerged() { return Status.Merged == status && !StringUtils.isEmpty(mergeSha); } diff --git a/src/main/java/com/gitblit/tickets/TicketLabel.java b/src/main/java/com/gitblit/tickets/TicketLabel.java index 686ce88b..a7f0ebec 100644 --- a/src/main/java/com/gitblit/tickets/TicketLabel.java +++ b/src/main/java/com/gitblit/tickets/TicketLabel.java @@ -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); } diff --git a/src/main/java/com/gitblit/tickets/TicketMilestone.java b/src/main/java/com/gitblit/tickets/TicketMilestone.java index c6b4fcca..dacedda6 100644 --- a/src/main/java/com/gitblit/tickets/TicketMilestone.java +++ b/src/main/java/com/gitblit/tickets/TicketMilestone.java @@ -38,6 +38,18 @@ public class TicketMilestone extends TicketLabel { 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; + } + public int getProgress() { int total = getTotalTickets(); if (total == 0) { diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java index c4fdeda5..d4c1bc40 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java @@ -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"); diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties index aeb2d9ef..1394890f 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties @@ -671,4 +671,10 @@ 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 +gb.notifyChangedOpenTickets = send notification for changed open tickets +gb.overdue = overdue +gb.openMilestones = open milestones +gb.closedMilestones = closed milestones
\ 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 index 00000000..0897ebee --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.html @@ -0,0 +1,39 @@ +<!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> <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>
+
+ <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" /> <input class="btn btn-danger" type="submit" value="Delete" wicket:message="value:gb.delete" wicket:id="delete" /></div>
+ </div>
+ </div>
+ </form>
+</div>
+</body>
+
+</wicket:extend>
+</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 index 00000000..b844442a --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/EditMilestonePage.java @@ -0,0 +1,189 @@ +/*
+ * 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.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;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+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.utils.StringUtils;
+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");
+ 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"));
+ 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));
+
+ form.add(new AjaxButton("save") {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+ 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
+ }
+ }
+ });
+ 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);
+
+ Button delete = new Button("delete") {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onSubmit() {
+ UserModel currentUser = GitBlitWebSession.get().getUser();
+ String createdBy = currentUser.username;
+ boolean notify = notificationModel.getObject();
+
+ if (app().tickets().deleteMilestone(getRepositoryModel(), oldName, createdBy, notify)) {
+ setResponsePage(TicketsPage.class, WicketUtils.newRepositoryParameter(repositoryName));
+ } else {
+ // TODO error processing
+ }
+ }
+ };
+ delete.setDefaultFormProcessing(false);
+ form.add(delete);
+ }
+
+ @Override
+ protected String getPageName() {
+ return getString("gb.editMilestone");
+ }
+
+ @Override
+ protected Class<? extends BasePage> getRepoNavPageClass() {
+ return TicketsPage.class;
+ }
+}
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 index 00000000..2ba5d5cc --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.html @@ -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> <span class="help-inline" wicket:id="dueFormat"></span></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>
\ 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 index 00000000..a9f76d3a --- /dev/null +++ b/src/main/java/com/gitblit/wicket/pages/NewMilestonePage.java @@ -0,0 +1,140 @@ +/*
+ * 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.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;
+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.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+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");
+ add(form);
+
+ nameModel = Model.of("");
+ dueModel = Model.of(new Date(System.currentTimeMillis() + TimeUtils.ONEDAY));
+
+ 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") {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ 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;
+ }
+
+ Date due = dueModel.getObject();
+
+ UserModel currentUser = GitBlitWebSession.get().getUser();
+ String createdBy = currentUser.username;
+
+ 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
+ }
+ }
+ });
+
+ 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;
+ }
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html index 7d13852b..a40d3126 100644 --- a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html +++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html @@ -139,14 +139,51 @@ </div>
<div class="tab-pane" id="milestones">
<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>
+ <span class="span12" style="padding-bottom:10px;" wicket:id="newMilestone"></span>
+ </div>
+
+ <div class="row">
+ <div class="span12"><h2><wicket:message key="gb.openMilestones"></wicket:message></h2></div>
+ <div class="span12"><hr/></div>
+ </div>
+ <div class="row">
+ <div class="span4" wicket:id="openMilestonesList" style="padding-bottom: 20px;">
+ <div wicket:id="entryPanel"></div>
+ </div>
+ </div>
+
+ <div class="row">
+ <div class="span12"><h2><wicket:message key="gb.closedMilestones"></wicket:message></h2></div>
+ <div class="span12"><hr/></div>
+ </div>
+ <div class="row">
+ <div class="span4" wicket:id="closedMilestonesList" style="padding-bottom: 15px;">
+ <div wicket:id="entryPanel"></div>
</div>
</div>
+
</div>
</div>
+<wicket:fragment wicket:id="milestoneListFragment">
+ <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> <span wicket:id="editMilestone"></span>
+ <div wicket:id="milestonePanel"></div>
+</wicket:fragment>
+
+<wicket:fragment wicket:id="openMilestoneFragment">
+ <div style="clear:both;padding-bottom: 10px;">
+ <div style="margin-bottom: 5px;" class="progress progress-success">
+ <div class="bar" wicket:id="progress"></div>
+ </div>
+ <div class="milestoneOverview">
+ <span wicket:id="openTickets" />,
+ <span wicket:id="closedTickets" />,
+ <span wicket:id="totalTickets" />
+ </div>
+ </div>
+</wicket:fragment>
+
<wicket:fragment wicket:id="noMilestoneFragment">
<table style="width: 100%;padding-bottom: 5px;">
<tbody>
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java index ca509e28..5973d473 100644 --- a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java +++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java @@ -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;
@@ -42,6 +43,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,38 +648,120 @@ public class TicketsPage extends TicketBasePage { };
add(ticketsView);
- List<TicketMilestone> allMilestones = app().tickets().getMilestones(getRepositoryModel());
- ListDataProvider<TicketMilestone> allMilestonesDp = new ListDataProvider<TicketMilestone>(allMilestones);
- DataView<TicketMilestone> milestonesList = new DataView<TicketMilestone>("milestoneList", allMilestonesDp) {
+ // 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> openMilestones = new ArrayList<TicketMilestone>();
+ List<TicketMilestone> closedMilestones = new ArrayList<TicketMilestone>();
+ for (TicketMilestone milestone : app().tickets().getMilestones(repositoryModel)) {
+ if (milestone.isOpen()) {
+ openMilestones.add(milestone);
+ } else {
+ closedMilestones.add(milestone);
+ }
+ }
+ Collections.sort(openMilestones, new Comparator<TicketMilestone>() {
+ @Override
+ public int compare(TicketMilestone o1, TicketMilestone o2) {
+ return o2.due.compareTo(o1.due);
+ }
+ });
+
+ Collections.sort(closedMilestones, new Comparator<TicketMilestone>() {
+ @Override
+ public int compare(TicketMilestone o1, TicketMilestone o2) {
+ return o2.due.compareTo(o1.due);
+ }
+ });
+
+ DataView<TicketMilestone> openMilestonesList = milestoneList("openMilestonesList", openMilestones, acceptingUpdates);
+ add(openMilestonesList);
+
+ DataView<TicketMilestone> closedMilestonesList = milestoneList("closedMilestonesList", closedMilestones, acceptingUpdates);
+ add(closedMilestonesList);
+ }
+
+ protected DataView<TicketMilestone> milestoneList(String wicketId, List<TicketMilestone> milestones, final boolean acceptingUpdates) {
+ ListDataProvider<TicketMilestone> milestonesDp = new ListDataProvider<TicketMilestone>(milestones);
+ DataView<TicketMilestone> milestonesList = new DataView<TicketMilestone>(wicketId, milestonesDp) {
private static final long serialVersionUID = 1L;
@Override
public void populateItem(final Item<TicketMilestone> item) {
+ Fragment entryPanel = new Fragment("entryPanel", "milestoneListFragment", this);
+ item.add(entryPanel);
+
final TicketMilestone tm = item.getModelObject();
- PageParameters params = queryParameters(null, tm.name, null, null, null, desc, 1);
- item.add(new LinkPanel("milestoneName", null, tm.name, TicketsPage.class, params).setRenderBodyOnly(true));
+ PageParameters params = queryParameters(null, tm.name, null, null, null, true, 1);
+ entryPanel.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);
+ entryPanel.add(stateLabel);
if (tm.due == null) {
- item.add(new Label("milestoneDue", getString("gb.notSpecified")));
+ entryPanel.add(new Label("milestoneDue", getString("gb.notSpecified")));
+ } else {
+ entryPanel.add(WicketUtils.createDatestampLabel("milestoneDue", tm.due, getTimeZone(), getTimeUtils()));
+ }
+ if (acceptingUpdates) {
+ entryPanel.add(new LinkPanel("editMilestone", null, getString("gb.edit"), EditMilestonePage.class,
+ WicketUtils.newObjectParameter(repositoryName, tm.name)));
+ } else {
+ entryPanel.add(new Label("editMilestone").setVisible(false));
+ }
+
+ if (tm.isOpen()) {
+ // re-load milestone with query results
+ TicketMilestone m = app().tickets().getMilestone(getRepositoryModel(), tm.name);
+
+ Fragment milestonePanel = new Fragment("milestonePanel", "openMilestoneFragment", this);
+ Label label = new Label("progress");
+ WicketUtils.setCssStyle(label, "width:" + tm.getProgress() + "%;");
+ milestonePanel.add(label);
+
+ milestonePanel.add(new LinkPanel("openTickets", null,
+ MessageFormat.format(getString("gb.nOpenTickets"), m.getOpenTickets()),
+ TicketsPage.class,
+ queryParameters(null, tm.name, openStatii, null, null, true, 1)));
+
+ milestonePanel.add(new LinkPanel("closedTickets", null,
+ MessageFormat.format(getString("gb.nClosedTickets"), m.getClosedTickets()),
+ TicketsPage.class,
+ queryParameters(null, tm.name, closedStatii, null, null, true, 1)));
+
+ milestonePanel.add(new Label("totalTickets", MessageFormat.format(getString("gb.nTotalTickets"), m.getTotalTickets())));
+ entryPanel.add(milestonePanel);
} else {
- item.add(WicketUtils.createDatestampLabel("milestoneDue", tm.due, getTimeZone(), getTimeUtils()));
+ entryPanel.add(new Label("milestonePanel").setVisible(false));
}
}
};
- add(milestonesList);
+ return milestonesList;
}
protected PageParameters queryParameters(
|