]> source.dussan.org Git - gitblit.git/commitdiff
Tickets - Priority, Severity options 220/head
authorPaul Martin <paul@paulsputer.com>
Mon, 29 Sep 2014 21:10:20 +0000 (22:10 +0100)
committerPaul Martin <paul@paulsputer.com>
Mon, 20 Oct 2014 21:54:54 +0000 (22:54 +0100)
+ Severity indicated via new character indicator and color of ticket icon on ticket list
+ Priority indicated via new priority icon and color on ticket list
+ Indexed as integers to provide sorting and maintain language neutral
index
+ Colours and indicator text controlled through CSS classes priority-<x> & severity-<x>
+ UITicketTest created to generate tickets of all types to ease debugging

20 files changed:
src/main/java/com/gitblit/models/TicketModel.java
src/main/java/com/gitblit/tickets/QueryResult.java
src/main/java/com/gitblit/tickets/TicketIndexer.java
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
src/main/java/com/gitblit/wicket/TicketsUI.java
src/main/java/com/gitblit/wicket/WicketUtils.java
src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
src/main/java/com/gitblit/wicket/pages/TicketPage.html
src/main/java/com/gitblit/wicket/pages/TicketPage.java
src/main/java/com/gitblit/wicket/pages/TicketsPage.java
src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
src/main/resources/gitblit.css
src/test/java/com/gitblit/tests/GitBlitSuite.java
src/test/java/com/gitblit/tests/TicketServiceTest.java
src/test/java/com/gitblit/tests/UITicketTest.java [new file with mode: 0644]

index 9bdb260654643ffe9c019b876d4026f393f5c50b..fd0b09eb9323abd2022ed83e315a9ad77ad18d99 100644 (file)
@@ -91,6 +91,10 @@ public class TicketModel implements Serializable, Comparable<TicketModel> {
 
        public Integer deletions;
 
+       public Priority priority;
+       
+       public Severity severity;
+       
        /**
         * Builds an effective ticket from the collection of changes.  A change may
         * Add or Subtract information from a ticket, but the collection of changes
@@ -141,6 +145,8 @@ public class TicketModel implements Serializable, Comparable<TicketModel> {
                changes = new ArrayList<Change>();
                status = Status.New;
                type = Type.defaultType;
+               priority = Priority.defaultPriority;
+               severity = Severity.defaultSeverity;
        }
 
        public boolean isOpen() {
@@ -517,6 +523,12 @@ public class TicketModel implements Serializable, Comparable<TicketModel> {
                                case mergeSha:
                                        mergeSha = toString(value);
                                        break;
+                               case priority:
+                                       priority = TicketModel.Priority.fromObject(value, priority);
+                                       break;
+                               case severity:
+                                       severity = TicketModel.Severity.fromObject(value, severity);
+                                       break;
                                default:
                                        // unknown
                                        break;
@@ -1183,7 +1195,7 @@ public class TicketModel implements Serializable, Comparable<TicketModel> {
 
        public static enum Field {
                title, body, responsible, type, status, milestone, mergeSha, mergeTo,
-               topic, labels, watchers, reviewers, voters, mentions;
+               topic, labels, watchers, reviewers, voters, mentions, priority, severity;
        }
 
        public static enum Type {
@@ -1310,4 +1322,110 @@ public class TicketModel implements Serializable, Comparable<TicketModel> {
                        return null;
                }
        }
+
+       public static enum Priority {
+               Low(-1), Normal(0), High(1), Urgent(2);
+
+               public static Priority defaultPriority = Normal;
+
+               final int value;
+
+               Priority(int value) {
+                       this.value = value;
+               }
+
+               public int getValue() {
+                       return value;
+               }
+               
+               public static Priority [] choices() {
+                       return new Priority [] { Urgent, High, Normal, Low };
+               }
+
+               @Override
+               public String toString() {
+                       return name().toLowerCase().replace('_', ' ');
+               }
+
+               public static Priority fromObject(Object o, Priority defaultPriority) {
+                       if (o instanceof Priority) {
+                               // cast and return
+                               return (Priority) o;
+                       } else if (o instanceof String) {
+                               // find by name
+                               for (Priority priority : values()) {
+                                       String str = o.toString();
+                                       if (priority.name().equalsIgnoreCase(str)
+                                                       || priority.toString().equalsIgnoreCase(str)) {
+                                               return priority;
+                                       }
+                               }
+                       } else if (o instanceof Number) {
+
+                               switch (((Number) o).intValue()) {
+                                       case -1: return Priority.Low;
+                                       case 0:  return Priority.Normal;
+                                       case 1:  return Priority.High;
+                                       case 2:  return Priority.Urgent;
+                                       default: return Priority.Normal;
+                               }
+                       }
+
+                       return defaultPriority;
+               }
+       }
+       
+       public static enum Severity {
+               Unrated(-1), Negligible(1), Minor(2), Serious(3), Critical(4), Catastrophic(5);
+
+               public static Severity defaultSeverity = Unrated;
+               
+               final int value;
+               
+               Severity(int value) {
+                       this.value = value;
+               }
+
+               public int getValue() {
+                       return value;
+               }
+               
+               public static Severity [] choices() {
+                       return new Severity [] { Unrated, Negligible, Minor, Serious, Critical, Catastrophic };
+               }
+
+               @Override
+               public String toString() {
+                       return name().toLowerCase().replace('_', ' ');
+               }
+               
+               public static Severity fromObject(Object o, Severity defaultSeverity) {
+                       if (o instanceof Severity) {
+                               // cast and return
+                               return (Severity) o;
+                       } else if (o instanceof String) {
+                               // find by name
+                               for (Severity severity : values()) {
+                                       String str = o.toString();
+                                       if (severity.name().equalsIgnoreCase(str)
+                                                       || severity.toString().equalsIgnoreCase(str)) {
+                                               return severity;
+                                       }
+                               }
+                       } else if (o instanceof Number) {
+                               
+                               switch (((Number) o).intValue()) {
+                                       case -1: return Severity.Unrated;
+                                       case 1:  return Severity.Negligible;
+                                       case 2:  return Severity.Minor;
+                                       case 3:  return Severity.Serious;
+                                       case 4:  return Severity.Critical;
+                                       case 5:  return Severity.Catastrophic;
+                                       default: return Severity.Unrated;
+                               }
+                       }
+
+                       return defaultSeverity;
+               }
+       }
 }
index 7a2b1abe0fb139dda944d0e307544ee0215a9511..f8d6d123245c05fc3411cd0272894daadb6063c3 100644 (file)
@@ -24,6 +24,8 @@ import java.util.List;
 import com.gitblit.models.TicketModel.Patchset;
 import com.gitblit.models.TicketModel.Status;
 import com.gitblit.models.TicketModel.Type;
+import com.gitblit.models.TicketModel.Priority;
+import com.gitblit.models.TicketModel.Severity;
 import com.gitblit.utils.StringUtils;
 
 /**
@@ -62,6 +64,8 @@ public class QueryResult implements Serializable {
        public int commentsCount;
        public int votesCount;
        public int approvalsCount;
+       public Priority priority;
+       public Severity severity;
 
        public int docId;
        public int totalResults;
index 11ea3a738ef774a700ebe24f12154d49ac7e2a08..e2d53af7b6d2fe082d30d1031006e35477954a33 100644 (file)
@@ -103,7 +103,10 @@ public class TicketIndexer {
                mergesha(Type.STRING),
                mergeto(Type.STRING),
                patchsets(Type.INT),
-               votes(Type.INT);
+               votes(Type.INT),
+               //NOTE: Indexing on the underlying value to allow flexibility on naming
+               priority(Type.INT),
+               severity(Type.INT);
 
                final Type fieldType;
 
@@ -519,6 +522,8 @@ public class TicketIndexer {
                toDocField(doc, Lucene.watchedby, StringUtils.flattenStrings(ticket.getWatchers(), ";").toLowerCase());
                toDocField(doc, Lucene.mentions, StringUtils.flattenStrings(ticket.getMentions(), ";").toLowerCase());
                toDocField(doc, Lucene.votes, ticket.getVoters().size());
+               toDocField(doc, Lucene.priority, ticket.priority.getValue());
+               toDocField(doc, Lucene.severity, ticket.severity.getValue());
 
                List<String> attachments = new ArrayList<String>();
                for (Attachment attachment : ticket.getAttachments()) {
@@ -600,6 +605,8 @@ public class TicketIndexer {
                result.participants = unpackStrings(doc, Lucene.participants);
                result.watchedby = unpackStrings(doc, Lucene.watchedby);
                result.mentions = unpackStrings(doc, Lucene.mentions);
+               result.priority = TicketModel.Priority.fromObject(unpackInt(doc, Lucene.priority), TicketModel.Priority.defaultPriority);
+               result.severity = TicketModel.Severity.fromObject(unpackInt(doc, Lucene.severity), TicketModel.Severity.defaultSeverity);
 
                if (!StringUtils.isEmpty(doc.get(Lucene.patchset.name()))) {
                        // unpack most recent patchset
index 2760f85f0678abecf674cc2cc31f0c13a84aa141..5326042a42416a003d884b942ad4e01b145a4cee 100644 (file)
@@ -742,4 +742,10 @@ gb.sshKeyCommentDescription = Enter an optional comment. If blank, the comment w
 gb.permission = Permission
 gb.sshKeyPermissionDescription = Specify the access permission for the SSH key
 gb.transportPreference = Transport Preference
-gb.transportPreferenceDescription = Set the transport that you prefer to use for cloning
\ No newline at end of file
+gb.transportPreferenceDescription = Set the transport that you prefer to use for cloning
+gb.priority = priority
+gb.severity = severity
+gb.sortHighestPriority = highest priority
+gb.sortLowestPriority = lowest priority
+gb.sortHighestSeverity = highest severity
+gb.sortLowestSeverity = lowest severity
index 347ac44d1dd27e7e958e6ee75fcc438ca5932c28..2367c9840ba052e9df55195e1aadf9f3de56d562 100644 (file)
@@ -21,6 +21,8 @@ import java.text.MessageFormat;
 import org.apache.wicket.markup.html.basic.Label;
 
 import com.gitblit.models.TicketModel;
+import com.gitblit.models.TicketModel.Priority;
+import com.gitblit.models.TicketModel.Severity;
 import com.gitblit.models.TicketModel.Status;
 import com.gitblit.models.TicketModel.Type;
 import com.gitblit.utils.StringUtils;
@@ -36,7 +38,7 @@ public class TicketsUI {
        public static final String [] openStatii = new String [] { Status.New.name().toLowerCase(), Status.Open.name().toLowerCase() };
 
        public static final String [] closedStatii = new String [] { "!" + Status.New.name().toLowerCase(), "!" + Status.Open.name().toLowerCase() };
-
+       
        public static Label getStateIcon(String wicketId, TicketModel ticket) {
                return getStateIcon(wicketId, ticket.type, ticket.status);
        }
@@ -48,28 +50,59 @@ public class TicketsUI {
                }
                switch (type) {
                case Proposal:
-                       WicketUtils.setCssClass(label, "fa fa-code-fork");
+                       WicketUtils.setCssClass(label, "fa fa-code-fork fa-fw");
                        break;
                case Bug:
-                       WicketUtils.setCssClass(label, "fa fa-bug");
+                       WicketUtils.setCssClass(label, "fa fa-bug fa-fw");
                        break;
                case Enhancement:
-                       WicketUtils.setCssClass(label, "fa fa-magic");
+                       WicketUtils.setCssClass(label, "fa fa-magic fa-fw");
                        break;
                case Question:
-                       WicketUtils.setCssClass(label, "fa fa-question");
+                       WicketUtils.setCssClass(label, "fa fa-question fa-fw");
                        break;
                case Maintenance:
-                       WicketUtils.setCssClass(label, "fa fa-cogs");
+                       WicketUtils.setCssClass(label, "fa fa-cogs fa-fw");
                        break;
                default:
                        // standard ticket
-                       WicketUtils.setCssClass(label, "fa fa-ticket");
+                       WicketUtils.setCssClass(label, "fa fa-ticket fa-fw");
                }
                WicketUtils.setHtmlTooltip(label, getTypeState(type, state));
+               
+               return label;
+       }
+       
+       public static Label getPriorityIcon(String wicketId, Priority priority) {
+               Label label = new Label(wicketId);
+               if (priority == null) {
+                       priority = Priority.defaultPriority;
+               }
+               switch (priority) {
+               case Urgent:
+                       WicketUtils.setCssClass(label, "fa fa-step-forward fa-rotate-270");
+                       break;
+               case High:
+                       WicketUtils.setCssClass(label, "fa fa-caret-up fa-lg");
+                       break;
+               case Low:
+                       WicketUtils.setCssClass(label, "fa fa-caret-down fa-lg");
+                       break;
+               default:
+               }
+               WicketUtils.setHtmlTooltip(label, priority.toString());
+               
                return label;
        }
+       
+       public static String getPriorityClass(Priority priority) {
+               return String.format("priority-%s", priority);
+       }
 
+       public static String getSeverityClass(Severity severity) {
+               return String.format("severity-%s", severity);  
+       }
+       
        public static String getTypeState(Type type, Status state) {
                return state.toString() + " " + type.toString();
        }
index d47390d44411391774b07fa13a663b79cf82e281..9a40931d3edf028ec6cc421f4775aa630c260a97 100644 (file)
@@ -29,12 +29,14 @@ import javax.servlet.http.HttpServletRequest;
 import org.apache.wicket.Component;\r
 import org.apache.wicket.PageParameters;\r
 import org.apache.wicket.Request;\r
+import org.apache.wicket.behavior.AttributeAppender;\r
 import org.apache.wicket.behavior.HeaderContributor;\r
 import org.apache.wicket.behavior.SimpleAttributeModifier;\r
 import org.apache.wicket.markup.html.IHeaderContributor;\r
 import org.apache.wicket.markup.html.IHeaderResponse;\r
 import org.apache.wicket.markup.html.basic.Label;\r
 import org.apache.wicket.markup.html.image.ContextImage;\r
+import org.apache.wicket.model.Model;\r
 import org.apache.wicket.protocol.http.WebRequest;\r
 import org.apache.wicket.resource.ContextRelativeResource;\r
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;\r
@@ -56,6 +58,10 @@ public class WicketUtils {
                container.add(new SimpleAttributeModifier("class", value));\r
        }\r
 \r
+       public static void addCssClass(Component container, String value) {\r
+               container.add(new AttributeAppender("class", new Model<String>(value), " "));\r
+       }\r
+       \r
        public static void setCssStyle(Component container, String value) {\r
                container.add(new SimpleAttributeModifier("style", value));\r
        }\r
index b5fe0ae539b1239456fbaa877ec89a170b8fbc5c..e11aed825ecb8c652dc7993f144e1d0acbcdad78 100644 (file)
@@ -41,6 +41,8 @@
                        <tr wicket:id="status"></tr>\r
                        <tr wicket:id="responsible"></tr>\r
                        <tr wicket:id="milestone"></tr>\r
+                       <tr><th><wicket:message key="gb.priority"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="priority"></select></td></tr>\r
+                       <tr><th><wicket:message key="gb.severity"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="severity"></select></td></tr>\r
                        <tr wicket:id="mergeto"></tr>\r
                </table>\r
        </div>\r
index c3d405bc08449bec40bb598557c473ac381b7edc..7c02c0beeb163c03184a53ba7c4ce89fe03ad7a8 100644 (file)
@@ -83,6 +83,10 @@ public class EditTicketPage extends RepositoryPage {
        private IModel<TicketMilestone> milestoneModel;\r
 \r
        private Label descriptionPreview;\r
+       \r
+       private IModel<TicketModel.Priority> priorityModel;\r
+       \r
+       private IModel<TicketModel.Severity> severityModel;\r
 \r
        public EditTicketPage(PageParameters params) {\r
                super(params);\r
@@ -117,6 +121,8 @@ public class EditTicketPage extends RepositoryPage {
                milestoneModel = Model.of();\r
                mergeToModel = Model.of(ticket.mergeTo == null ? getRepositoryModel().mergeTo : ticket.mergeTo);\r
                statusModel = Model.of(ticket.status);\r
+               priorityModel = Model.of(ticket.priority);\r
+               severityModel = Model.of(ticket.severity);\r
 \r
                setStatelessHint(false);\r
                setOutputMarkupId(true);\r
@@ -219,6 +225,14 @@ public class EditTicketPage extends RepositoryPage {
                        milestone.add(new DropDownChoice<TicketMilestone>("milestone", milestoneModel, milestones));\r
                        form.add(milestone.setVisible(!milestones.isEmpty()));\r
 \r
+                       // priority\r
+                       List<TicketModel.Priority> priorityChoices = Arrays.asList(TicketModel.Priority.choices());\r
+                       form.add(new DropDownChoice<TicketModel.Priority>("priority", priorityModel, priorityChoices));\r
+                       \r
+                       // severity\r
+                       List<TicketModel.Severity> severityChoices = Arrays.asList(TicketModel.Severity.choices());\r
+                       form.add(new DropDownChoice<TicketModel.Severity>("severity", severityModel, severityChoices));\r
+                                               \r
                        // mergeTo (integration branch)\r
                        List<String> branches = new ArrayList<String>();\r
                        for (String branch : getRepositoryModel().getLocalBranches()) {\r
@@ -315,7 +329,19 @@ public class EditTicketPage extends RepositoryPage {
                                                change.setField(Field.milestone, milestone.name);\r
                                        }\r
                                }\r
+                               \r
+                               TicketModel.Priority priority = priorityModel.getObject();\r
+                               if (!ticket.priority.equals(priority))\r
+                               {\r
+                                       change.setField(Field.priority, priority);\r
+                               }\r
 \r
+                               TicketModel.Severity severity = severityModel.getObject();\r
+                               if (!ticket.severity.equals(severity))\r
+                               {\r
+                                       change.setField(Field.severity, severity);\r
+                               }\r
+                               \r
                                String mergeTo = mergeToModel.getObject();\r
                                if ((StringUtils.isEmpty(ticket.mergeTo) && !StringUtils.isEmpty(mergeTo))\r
                                                || (!StringUtils.isEmpty(mergeTo) && !mergeTo.equals(ticket.mergeTo))) {\r
index 1396a5e50f793f08a820a91de405a3f3b6c68018..187302f5c63fdd63f6a6fb1d32ef5d74c8a51c7f 100644 (file)
@@ -230,6 +230,10 @@ public class MyTicketsPage extends RootPage {
                sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
                sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
                sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));
+               sortChoices.add(new TicketSort(getString("gb.sortHighestPriority"), Lucene.priority.name(), true));
+               sortChoices.add(new TicketSort(getString("gb.sortLowestPriority"), Lucene.priority.name(), false));
+               sortChoices.add(new TicketSort(getString("gb.sortHighestSeverity"), Lucene.severity.name(), true));
+               sortChoices.add(new TicketSort(getString("gb.sortLowestSeverity"), Lucene.severity.name(), false));
 
                TicketSort currentSort = sortChoices.get(0);
                for (TicketSort ts : sortChoices) {
index 447c6aa4f396bd3caa27b2d8af0aae1fe37a162c..7b71ada078e14ae9807070686c4858f19a68aaae 100644 (file)
@@ -41,6 +41,8 @@
                        <tr><th><wicket:message key="gb.type"></wicket:message><span style="color:red;">*</span></th><td class="edit"><select class="input-large" wicket:id="type"></select></td></tr>\r
                        <tr wicket:id="responsible"></tr>\r
                        <tr wicket:id="milestone"></tr>\r
+                       <tr><th><wicket:message key="gb.priority"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="priority"></select></td></tr>\r
+                       <tr><th><wicket:message key="gb.severity"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="severity"></select></td></tr>\r
                        <tr wicket:id="mergeto"></tr>\r
                </table>\r
        </div>\r
index 8f28055a24d60fd5a3a70eef852c3783735b8c56..ea3e960262a01efb4704a1a81eea50d0ce8122bb 100644 (file)
@@ -75,6 +75,10 @@ public class NewTicketPage extends RepositoryPage {
        private IModel<TicketMilestone> milestoneModel;\r
 \r
        private Label descriptionPreview;\r
+       \r
+       private IModel<TicketModel.Priority> priorityModel;\r
+       \r
+       private IModel<TicketModel.Severity> severityModel;\r
 \r
        public NewTicketPage(PageParameters params) {\r
                super(params);\r
@@ -95,6 +99,8 @@ public class NewTicketPage extends RepositoryPage {
                mergeToModel = Model.of(Repository.shortenRefName(getRepositoryModel().mergeTo));\r
                responsibleModel = Model.of();\r
                milestoneModel = Model.of();\r
+               severityModel = Model.of(TicketModel.Severity.defaultSeverity);\r
+               priorityModel = Model.of(TicketModel.Priority.defaultPriority);\r
 \r
                setStatelessHint(false);\r
                setOutputMarkupId(true);\r
@@ -152,6 +158,12 @@ public class NewTicketPage extends RepositoryPage {
                        milestone.add(new DropDownChoice<TicketMilestone>("milestone", milestoneModel, milestones));\r
                        form.add(milestone.setVisible(!milestones.isEmpty()));\r
 \r
+                       // priority\r
+                       form.add(new DropDownChoice<TicketModel.Priority>("priority", priorityModel, Arrays.asList(TicketModel.Priority.choices())));\r
+                       \r
+                       //severity\r
+                       form.add(new DropDownChoice<TicketModel.Severity>("severity", severityModel, Arrays.asList(TicketModel.Severity.choices())));\r
+                       \r
                        // integration branch\r
                        List<String> branches = new ArrayList<String>();\r
                        for (String branch : getRepositoryModel().getLocalBranches()) {\r
@@ -211,6 +223,20 @@ public class NewTicketPage extends RepositoryPage {
                                if (milestone != null) {\r
                                        change.setField(Field.milestone, milestone.name);\r
                                }\r
+                               \r
+                               // severity\r
+                               TicketModel.Severity severity = TicketModel.Severity.defaultSeverity;\r
+                               if (severityModel.getObject() != null) {\r
+                                       severity = severityModel.getObject();\r
+                               }\r
+                               change.setField(Field.severity, severity);\r
+                               \r
+                               // priority\r
+                               TicketModel.Priority priority = TicketModel.Priority.defaultPriority;\r
+                               if (priorityModel.getObject() != null) {\r
+                                       priority = priorityModel.getObject();\r
+                               }\r
+                               change.setField(Field.priority, priority);\r
 \r
                                // integration branch\r
                                String mergeTo = mergeToModel.getObject();\r
index f3f38ec37ccc70ee562d1f0506e0551b614bd749..f3c6b2a8f1e58794edf276c330b3cbc5e464cac7 100644 (file)
@@ -67,6 +67,8 @@
                                <div style="border: 1px solid #ccc;padding: 10px;margin: 5px 0px;">\r
                                        <table class="summary" style="width: 100%">\r
                                                <tr><th><wicket:message key="gb.type"></wicket:message></th><td><span wicket:id="ticketType">[type]</span></td></tr>\r
+                                               <tr><th><wicket:message key="gb.priority"></wicket:message></th><td><span wicket:id="priority">[priority]</span></td></tr>\r
+                                               <tr><th><wicket:message key="gb.severity"></wicket:message></th><td><span wicket:id="severity">[severity]</span></td></tr>\r
                                                <tr><th><wicket:message key="gb.topic"></wicket:message></th><td><span wicket:id="ticketTopic">[topic]</span></td></tr>\r
                                                <tr><th><wicket:message key="gb.responsible"></wicket:message></th><td><span wicket:id="responsible">[responsible]</span></td></tr>\r
                                                <tr><th><wicket:message key="gb.milestone"></wicket:message></th><td><span wicket:id="milestone">[milestone]</span></td></tr>\r
index c5b0f31b286c9b9a036e340c56f5deaf4d177669..b140bfca718b4516606cfb893ab48fb2b76cec39 100644 (file)
@@ -519,6 +519,10 @@ public class TicketPage extends RepositoryPage {
                 * TICKET METADATA\r
                 */\r
                add(new Label("ticketType", ticket.type.toString()));\r
+               \r
+               add(new Label("priority", ticket.priority.toString()));\r
+               add(new Label("severity", ticket.severity.toString()));\r
+               \r
                if (StringUtils.isEmpty(ticket.topic)) {\r
                        add(new Label("ticketTopic").setVisible(false));\r
                } else {\r
@@ -527,6 +531,8 @@ public class TicketPage extends RepositoryPage {
                        String safeTopic = app().xssFilter().relaxed(topic);\r
                        add(new Label("ticketTopic", safeTopic).setEscapeModelStrings(false));\r
                }\r
+               \r
+               \r
 \r
 \r
                /*\r
index 745cabf4839e8f656636e167cd741b3490096960..ecfed250f553feea4deb7314dcdf5a817a729017 100644 (file)
@@ -464,7 +464,11 @@ public class TicketsPage extends RepositoryPage {
                sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));\r
                sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));\r
                sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));\r
-\r
+               sortChoices.add(new TicketSort(getString("gb.sortHighestPriority"), Lucene.priority.name(), true));\r
+               sortChoices.add(new TicketSort(getString("gb.sortLowestPriority"), Lucene.priority.name(), false));\r
+               sortChoices.add(new TicketSort(getString("gb.sortHighestSeverity"), Lucene.severity.name(), true));\r
+               sortChoices.add(new TicketSort(getString("gb.sortLowestSeverity"), Lucene.severity.name(), false));\r
+               \r
                TicketSort currentSort = sortChoices.get(0);\r
                for (TicketSort ts : sortChoices) {\r
                        if (ts.sortBy.equals(sortBy) && desc == ts.desc) {\r
index 30f503670d9df6663a3943e645c0755928c6d626..659baeacb61643736b242b083ae43ae44f4033de 100644 (file)
@@ -30,6 +30,9 @@
                <td class="hidden-phone ticket-list-state">\r
                                <i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>\r
                </td>\r
+               <td class="ticket-list-priority">\r
+                               <div wicket:id="priority"></div>\r
+               </td>\r
                <td class="ticket-list-state">\r
                                <div wicket:id="status"></div>\r
                </td>\r
index cc0b57a8a49bb18152a77ffd49654782253239a4..b4c43cb8f1a667a19efe24108af1bbf107ae52ed 100644 (file)
@@ -53,7 +53,7 @@ import com.gitblit.wicket.pages.UserPage;
 public class TicketListPanel extends BasePanel {
 
        private static final long serialVersionUID = 1L;
-
+       
        public TicketListPanel(String wicketId, List<QueryResult> list, final boolean showSwatch, final boolean showRepository) {
                super(wicketId);
 
@@ -83,7 +83,10 @@ public class TicketListPanel extends BasePanel {
                                        item.add(new Label("ticketsLink").setVisible(false));
                                }
 
-                               item.add(TicketsUI.getStateIcon("state", ticket.type, ticket.status));
+                               Label icon = TicketsUI.getStateIcon("state", ticket.type, ticket.status);
+                               WicketUtils.addCssClass(icon, TicketsUI.getSeverityClass(ticket.severity));
+                               item.add(icon);
+                               
                                item.add(new Label("id", "" + ticket.number));
                                UserModel creator = app().users().getUserModel(ticket.createdBy);
                                if (creator != null) {
@@ -167,6 +170,11 @@ public class TicketListPanel extends BasePanel {
                                // watching indicator
                                item.add(new Label("watching").setVisible(ticket.isWatching(GitBlitWebSession.get().getUsername())));
 
+                               // priority indicator
+                               Label priorityIcon = TicketsUI.getPriorityIcon("priority", ticket.priority);
+                               WicketUtils.addCssClass(priorityIcon, TicketsUI.getPriorityClass(ticket.priority));
+                               item.add(priorityIcon.setVisible(true));
+                               
                                // status indicator
                                String css = TicketsUI.getLozengeClass(ticket.status, true);
                                Label l = new Label("status", ticket.status.toString());
index 748a3198b41e79e1c73c8382ec5cbe02f9597583..ba6f534a7d59f17ed0a3b4cfb77886f2c4bae498 100644 (file)
@@ -782,6 +782,10 @@ pre.prettyprint ol {
 
 td.ticket-list-state {
        vertical-align: middle;
+}\r
+\r
+td.ticket-list-priority {\r
+       vertical-align: middle;\r
 }
 
 .ticket-list-details {
@@ -2079,4 +2083,72 @@ div.markdown table.text th, div.markdown table.text td {
     background-color: #fff;
     border-color: #ece7e2;
     color: #815b3a;
-}
\ No newline at end of file
+}\r
+.severity-catastrophic {\r
+       color:#CC79A7;\r
+}\r
+.severity-catastrophic:after {\r
+       font-family: Helvetica,arial,freesans,clean,sans-serif ;\r
+       content: "Ca";\r
+       font-weight:900;\r
+       font-size:.6em; \r
+       font-variant:small-caps;\r
+       display:flex;\r
+}\r
+.severity-critical {\r
+       color:#D55E00;\r
+}\r
+.severity-critical:after {\r
+       font-family: Helvetica,arial,freesans,clean,sans-serif ;\r
+       content: "c";\r
+       font-weight:900;\r
+       font-size:.6em; \r
+       font-variant:small-caps;\r
+       display:flex;\r
+}\r
+.severity-serious {\r
+       color:#E69F00;\r
+}\r
+.severity-serious:after {\r
+       font-family: Helvetica,arial,freesans,clean,sans-serif ;\r
+       content: "s";\r
+       font-weight:900;\r
+       font-size:.6em; \r
+       font-variant:small-caps;\r
+       display:flex;\r
+}\r
+.severity-minor {\r
+       color:#0072B2;\r
+}\r
+.severity-minor:after {\r
+       font-family: Helvetica,arial,freesans,clean,sans-serif ;\r
+       content: "m";\r
+       font-weight:900;\r
+       font-size:.6em; \r
+       font-variant:small-caps;\r
+       display:flex;\r
+}\r
+.severity-negligible {\r
+       color:#009E73;\r
+}\r
+.severity-negligible:after {\r
+       font-family: Helvetica,arial,freesans,clean,sans-serif ;\r
+       content: "n";\r
+       font-weight:900;\r
+       font-size:.6em; \r
+       font-variant:small-caps;\r
+       display:flex;\r
+}\r
+.severity-unrated {\r
+}\r
+.priority-urgent {\r
+       color:#CC79A7;\r
+}\r
+.priority-high {\r
+       color:#D55E00;\r
+}\r
+.priority-normal {\r
+}\r
+.priority-low {\r
+       color:#0072B2;\r
+}\r
index 5a7dcea12ab311c46e7ce6ac35af5654f57b12c9..f2dfcc0cd4f919753a4032add5dccef3e1082f67 100644 (file)
@@ -65,7 +65,7 @@ import com.gitblit.utils.JGitUtils;
                FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class,\r
                ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class,
                BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class,\r
-               SshKeysDispatcherTest.class })
+               SshKeysDispatcherTest.class, UITicketTest.class })
 public class GitBlitSuite {\r
 \r
        public static final File BASEFOLDER = new File("data");\r
index 1676e341fb9ab7bce06bbb491af8e14f29d40dda..c654383d9d172d3472221b3406381eb66facd8c5 100644 (file)
@@ -293,9 +293,47 @@ public abstract class TicketServiceTest extends GitblitUnitTest {
                        assertTrue("failed to delete label " + label.name, service.deleteLabel(getRepository(), label.name, "lucifer"));\r
                }\r
        }\r
-\r
-\r
-\r
+       \r
+       @Test\r
+       public void testPriorityAndSeverity() throws Exception {\r
+               // C1: create and insert a ticket\r
+               Change c1 = newChange("testPriorityAndSeverity() " + Long.toHexString(System.currentTimeMillis()));\r
+               TicketModel ticket = service.createTicket(getRepository(), c1);\r
+               assertTrue(ticket.number > 0);\r
+               assertEquals(TicketModel.Priority.Normal, ticket.priority);\r
+               assertEquals(TicketModel.Severity.Unrated, ticket.severity);\r
+               \r
+               TicketModel constructed = service.getTicket(getRepository(), ticket.number);\r
+               compare(ticket, constructed);\r
+               \r
+               // C2: Change Priority max\r
+               Change c2 = new Change("C2");\r
+               c2.setField(Field.priority, TicketModel.Priority.Urgent);\r
+               constructed = service.updateTicket(getRepository(), ticket.number, c2);\r
+               assertNotNull(constructed);\r
+               assertEquals(2, constructed.changes.size());\r
+               assertEquals(TicketModel.Priority.Urgent, constructed.priority);\r
+               assertEquals(TicketModel.Severity.Unrated, constructed.severity);\r
+               \r
+               // C3: Change Severity max\r
+               Change c3 = new Change("C3");\r
+               c3.setField(Field.severity, TicketModel.Severity.Catastrophic);\r
+               constructed = service.updateTicket(getRepository(), ticket.number, c3);\r
+               assertNotNull(constructed);\r
+               assertEquals(3, constructed.changes.size());\r
+               assertEquals(TicketModel.Priority.Urgent, constructed.priority);\r
+               assertEquals(TicketModel.Severity.Catastrophic, constructed.severity);\r
+               \r
+               // C4: Change Priority min\r
+               Change c4 = new Change("C3");\r
+               c4.setField(Field.priority, TicketModel.Priority.Low);\r
+               constructed = service.updateTicket(getRepository(), ticket.number, c4);\r
+               assertNotNull(constructed);\r
+               assertEquals(4, constructed.changes.size());\r
+               assertEquals(TicketModel.Priority.Low, constructed.priority);\r
+               assertEquals(TicketModel.Severity.Catastrophic, constructed.severity);\r
+       }\r
+       \r
        private Change newChange(String summary) {\r
                Change change = new Change("C1");\r
                change.setField(Field.title, summary);\r
diff --git a/src/test/java/com/gitblit/tests/UITicketTest.java b/src/test/java/com/gitblit/tests/UITicketTest.java
new file mode 100644 (file)
index 0000000..54aa1e1
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * 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.tests;
+
+import java.io.File;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.bouncycastle.util.Arrays;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.INotificationManager;
+import com.gitblit.manager.IPluginManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.manager.NotificationManager;
+import com.gitblit.manager.PluginManager;
+import com.gitblit.manager.RepositoryManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.manager.UserManager;
+import com.gitblit.models.Mailing;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.TicketModel;
+import com.gitblit.models.TicketModel.Attachment;
+import com.gitblit.models.TicketModel.Change;
+import com.gitblit.models.TicketModel.Field;
+import com.gitblit.models.TicketModel.Patchset;
+import com.gitblit.models.TicketModel.Priority;
+import com.gitblit.models.TicketModel.Severity;
+import com.gitblit.models.TicketModel.Status;
+import com.gitblit.models.TicketModel.Type;
+import com.gitblit.tests.mock.MemorySettings;
+import com.gitblit.tickets.ITicketService;
+import com.gitblit.tickets.ITicketService.TicketFilter;
+import com.gitblit.tickets.QueryResult;
+import com.gitblit.tickets.TicketIndexer.Lucene;
+import com.gitblit.tickets.BranchTicketService;
+import com.gitblit.tickets.TicketLabel;
+import com.gitblit.tickets.TicketMilestone;
+import com.gitblit.tickets.TicketNotifier;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.XssFilter;
+import com.gitblit.utils.XssFilter.AllowXssFilter;
+
+/**
+ * Generates the range of tickets to ease testing of the look and feel of tickets
+ */
+public class UITicketTest extends GitblitUnitTest {
+
+       private ITicketService service; 
+       final String repoName = "UITicketTest.git";
+       final RepositoryModel repo = new RepositoryModel(repoName, null, null, null);
+       
+       protected ITicketService getService(boolean deleteAll) throws Exception {
+
+               IStoredSettings settings = getSettings(deleteAll);
+               XssFilter xssFilter = new AllowXssFilter();
+               IRuntimeManager runtimeManager = new RuntimeManager(settings, xssFilter).start();
+               IPluginManager pluginManager = new PluginManager(runtimeManager).start();
+               INotificationManager notificationManager = new NotificationManager(settings).start();
+               IUserManager userManager = new UserManager(runtimeManager, pluginManager).start();
+               IRepositoryManager repositoryManager = new RepositoryManager(runtimeManager, pluginManager, userManager).start();
+
+               BranchTicketService service = new BranchTicketService(
+                               runtimeManager,
+                               pluginManager,
+                               notificationManager,
+                               userManager,
+                               repositoryManager).start();
+
+               if (deleteAll) {
+                       service.deleteAll(repo);
+               }
+               return service;
+       }
+       
+       protected IStoredSettings getSettings(boolean deleteAll) throws Exception {
+               File dir = new File(GitBlitSuite.REPOSITORIES, repoName);
+               if (deleteAll) {
+                       FileUtils.deleteDirectory(dir);
+                       JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repoName).close();
+               }
+               
+               File luceneDir = new File(dir, "tickets/lucene");
+               luceneDir.mkdirs();
+
+               Map<String, Object> map = new HashMap<String, Object>();
+               map.put(Keys.git.repositoriesFolder, GitBlitSuite.REPOSITORIES.getAbsolutePath());
+               map.put(Keys.tickets.indexFolder, luceneDir.getAbsolutePath());
+
+               IStoredSettings settings = new MemorySettings(map);
+               return settings;
+       }
+
+       @Before
+       public void setup() throws Exception {
+               service = getService(true);
+       }
+
+       @After
+       public void cleanup() {
+               service.stop();
+       }
+
+       @Test
+       public void UITicketOptions() throws Exception {
+               
+               for (TicketModel.Type t : TicketModel.Type.values())
+               {
+                       for (TicketModel.Priority p : TicketModel.Priority.values())
+                       {
+                               for (TicketModel.Severity s : TicketModel.Severity.values())
+                               {
+                                       assertNotNull(service.createTicket(repo, newChange(t, p, s)));
+                               }
+                       }       
+               }
+       }
+       
+       private Change newChange(Type type, Priority priority, Severity severity) {
+               Change change = new Change("JUnit");
+               change.setField(Field.title, String.format("Type: %s | Priority: %s | Severity: %s", type, priority, severity));
+               change.setField(Field.type, type);
+               change.setField(Field.severity, severity);
+               change.setField(Field.priority, priority);
+               return change;
+       }
+
+}
\ No newline at end of file