Paul Martin
2014-09-29 f9c78c0ccc709509cdf7f83c45c898883d329db2
Tickets - Priority, Severity options

+ 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
1 files added
19 files modified
554 ■■■■■ changed files
src/main/java/com/gitblit/models/TicketModel.java 120 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/tickets/QueryResult.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/tickets/TicketIndexer.java 9 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/TicketsUI.java 47 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/WicketUtils.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EditTicketPage.html 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EditTicketPage.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/NewTicketPage.html 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/NewTicketPage.java 26 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/TicketPage.html 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/TicketPage.java 6 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/TicketsPage.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/TicketListPanel.html 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/TicketListPanel.java 12 ●●●● patch | view | raw | blame | history
src/main/resources/gitblit.css 74 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/GitBlitSuite.java 2 ●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/TicketServiceTest.java 44 ●●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/UITicketTest.java 151 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/TicketModel.java
@@ -91,6 +91,10 @@
    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 @@
        changes = new ArrayList<Change>();
        status = Status.New;
        type = Type.defaultType;
        priority = Priority.defaultPriority;
        severity = Severity.defaultSeverity;
    }
    public boolean isOpen() {
@@ -516,6 +522,12 @@
                    break;
                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
@@ -1183,7 +1195,7 @@
    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 @@
            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;
        }
    }
}
src/main/java/com/gitblit/tickets/QueryResult.java
@@ -24,6 +24,8 @@
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 int commentsCount;
    public int votesCount;
    public int approvalsCount;
    public Priority priority;
    public Severity severity;
    public int docId;
    public int totalResults;
src/main/java/com/gitblit/tickets/TicketIndexer.java
@@ -103,7 +103,10 @@
        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 @@
        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 @@
        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
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -742,4 +742,10 @@
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
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
src/main/java/com/gitblit/wicket/TicketsUI.java
@@ -21,6 +21,8 @@
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 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 @@
        }
        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();
    }
src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -29,12 +29,14 @@
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.Request;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.image.ContextImage;
import org.apache.wicket.model.Model;
import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.resource.ContextRelativeResource;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
@@ -56,6 +58,10 @@
        container.add(new SimpleAttributeModifier("class", value));
    }
    public static void addCssClass(Component container, String value) {
        container.add(new AttributeAppender("class", new Model<String>(value), " "));
    }
    public static void setCssStyle(Component container, String value) {
        container.add(new SimpleAttributeModifier("style", value));
    }
src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
@@ -41,6 +41,8 @@
            <tr wicket:id="status"></tr>
            <tr wicket:id="responsible"></tr>
            <tr wicket:id="milestone"></tr>
            <tr><th><wicket:message key="gb.priority"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="priority"></select></td></tr>
            <tr><th><wicket:message key="gb.severity"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="severity"></select></td></tr>
            <tr wicket:id="mergeto"></tr>
        </table>
    </div>
src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
@@ -83,6 +83,10 @@
    private IModel<TicketMilestone> milestoneModel;
    private Label descriptionPreview;
    private IModel<TicketModel.Priority> priorityModel;
    private IModel<TicketModel.Severity> severityModel;
    public EditTicketPage(PageParameters params) {
        super(params);
@@ -117,6 +121,8 @@
        milestoneModel = Model.of();
        mergeToModel = Model.of(ticket.mergeTo == null ? getRepositoryModel().mergeTo : ticket.mergeTo);
        statusModel = Model.of(ticket.status);
        priorityModel = Model.of(ticket.priority);
        severityModel = Model.of(ticket.severity);
        setStatelessHint(false);
        setOutputMarkupId(true);
@@ -219,6 +225,14 @@
            milestone.add(new DropDownChoice<TicketMilestone>("milestone", milestoneModel, milestones));
            form.add(milestone.setVisible(!milestones.isEmpty()));
            // priority
            List<TicketModel.Priority> priorityChoices = Arrays.asList(TicketModel.Priority.choices());
            form.add(new DropDownChoice<TicketModel.Priority>("priority", priorityModel, priorityChoices));
            // severity
            List<TicketModel.Severity> severityChoices = Arrays.asList(TicketModel.Severity.choices());
            form.add(new DropDownChoice<TicketModel.Severity>("severity", severityModel, severityChoices));
            // mergeTo (integration branch)
            List<String> branches = new ArrayList<String>();
            for (String branch : getRepositoryModel().getLocalBranches()) {
@@ -315,7 +329,19 @@
                        change.setField(Field.milestone, milestone.name);
                    }
                }
                TicketModel.Priority priority = priorityModel.getObject();
                if (!ticket.priority.equals(priority))
                {
                    change.setField(Field.priority, priority);
                }
                TicketModel.Severity severity = severityModel.getObject();
                if (!ticket.severity.equals(severity))
                {
                    change.setField(Field.severity, severity);
                }
                String mergeTo = mergeToModel.getObject();
                if ((StringUtils.isEmpty(ticket.mergeTo) && !StringUtils.isEmpty(mergeTo))
                        || (!StringUtils.isEmpty(mergeTo) && !mergeTo.equals(ticket.mergeTo))) {
src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
@@ -230,6 +230,10 @@
        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) {
src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
@@ -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>
            <tr wicket:id="responsible"></tr>
            <tr wicket:id="milestone"></tr>
            <tr><th><wicket:message key="gb.priority"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="priority"></select></td></tr>
            <tr><th><wicket:message key="gb.severity"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="severity"></select></td></tr>
            <tr wicket:id="mergeto"></tr>
        </table>
    </div>
src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
@@ -75,6 +75,10 @@
    private IModel<TicketMilestone> milestoneModel;
    private Label descriptionPreview;
    private IModel<TicketModel.Priority> priorityModel;
    private IModel<TicketModel.Severity> severityModel;
    public NewTicketPage(PageParameters params) {
        super(params);
@@ -95,6 +99,8 @@
        mergeToModel = Model.of(Repository.shortenRefName(getRepositoryModel().mergeTo));
        responsibleModel = Model.of();
        milestoneModel = Model.of();
        severityModel = Model.of(TicketModel.Severity.defaultSeverity);
        priorityModel = Model.of(TicketModel.Priority.defaultPriority);
        setStatelessHint(false);
        setOutputMarkupId(true);
@@ -152,6 +158,12 @@
            milestone.add(new DropDownChoice<TicketMilestone>("milestone", milestoneModel, milestones));
            form.add(milestone.setVisible(!milestones.isEmpty()));
            // priority
            form.add(new DropDownChoice<TicketModel.Priority>("priority", priorityModel, Arrays.asList(TicketModel.Priority.choices())));
            //severity
            form.add(new DropDownChoice<TicketModel.Severity>("severity", severityModel, Arrays.asList(TicketModel.Severity.choices())));
            // integration branch
            List<String> branches = new ArrayList<String>();
            for (String branch : getRepositoryModel().getLocalBranches()) {
@@ -211,6 +223,20 @@
                if (milestone != null) {
                    change.setField(Field.milestone, milestone.name);
                }
                // severity
                TicketModel.Severity severity = TicketModel.Severity.defaultSeverity;
                if (severityModel.getObject() != null) {
                    severity = severityModel.getObject();
                }
                change.setField(Field.severity, severity);
                // priority
                TicketModel.Priority priority = TicketModel.Priority.defaultPriority;
                if (priorityModel.getObject() != null) {
                    priority = priorityModel.getObject();
                }
                change.setField(Field.priority, priority);
                // integration branch
                String mergeTo = mergeToModel.getObject();
src/main/java/com/gitblit/wicket/pages/TicketPage.html
@@ -67,6 +67,8 @@
                <div style="border: 1px solid #ccc;padding: 10px;margin: 5px 0px;">
                    <table class="summary" style="width: 100%">
                        <tr><th><wicket:message key="gb.type"></wicket:message></th><td><span wicket:id="ticketType">[type]</span></td></tr>
                        <tr><th><wicket:message key="gb.priority"></wicket:message></th><td><span wicket:id="priority">[priority]</span></td></tr>
                        <tr><th><wicket:message key="gb.severity"></wicket:message></th><td><span wicket:id="severity">[severity]</span></td></tr>
                        <tr><th><wicket:message key="gb.topic"></wicket:message></th><td><span wicket:id="ticketTopic">[topic]</span></td></tr>
                        <tr><th><wicket:message key="gb.responsible"></wicket:message></th><td><span wicket:id="responsible">[responsible]</span></td></tr>
                        <tr><th><wicket:message key="gb.milestone"></wicket:message></th><td><span wicket:id="milestone">[milestone]</span></td></tr>
src/main/java/com/gitblit/wicket/pages/TicketPage.java
@@ -519,6 +519,10 @@
         * TICKET METADATA
         */
        add(new Label("ticketType", ticket.type.toString()));
        add(new Label("priority", ticket.priority.toString()));
        add(new Label("severity", ticket.severity.toString()));
        if (StringUtils.isEmpty(ticket.topic)) {
            add(new Label("ticketTopic").setVisible(false));
        } else {
@@ -527,6 +531,8 @@
            String safeTopic = app().xssFilter().relaxed(topic);
            add(new Label("ticketTopic", safeTopic).setEscapeModelStrings(false));
        }
        /*
src/main/java/com/gitblit/wicket/pages/TicketsPage.java
@@ -464,7 +464,11 @@
        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) {
            if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
@@ -30,6 +30,9 @@
            <td class="hidden-phone ticket-list-state">
                   <i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>
            </td>
            <td class="ticket-list-priority">
                   <div wicket:id="priority"></div>
            </td>
            <td class="ticket-list-state">
                   <div wicket:id="status"></div>
            </td>
src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
@@ -53,7 +53,7 @@
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 @@
                    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 @@
                // 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());
src/main/resources/gitblit.css
@@ -782,6 +782,10 @@
td.ticket-list-state {
    vertical-align: middle;
}
td.ticket-list-priority {
    vertical-align: middle;
}
.ticket-list-details {
@@ -2079,4 +2083,72 @@
    background-color: #fff;
    border-color: #ece7e2;
    color: #815b3a;
}
}
.severity-catastrophic {
    color:#CC79A7;
}
.severity-catastrophic:after {
    font-family: Helvetica,arial,freesans,clean,sans-serif ;
    content: "Ca";
    font-weight:900;
    font-size:.6em;
    font-variant:small-caps;
    display:flex;
}
.severity-critical {
    color:#D55E00;
}
.severity-critical:after {
    font-family: Helvetica,arial,freesans,clean,sans-serif ;
    content: "c";
    font-weight:900;
    font-size:.6em;
    font-variant:small-caps;
    display:flex;
}
.severity-serious {
    color:#E69F00;
}
.severity-serious:after {
    font-family: Helvetica,arial,freesans,clean,sans-serif ;
    content: "s";
    font-weight:900;
    font-size:.6em;
    font-variant:small-caps;
    display:flex;
}
.severity-minor {
    color:#0072B2;
}
.severity-minor:after {
    font-family: Helvetica,arial,freesans,clean,sans-serif ;
    content: "m";
    font-weight:900;
    font-size:.6em;
    font-variant:small-caps;
    display:flex;
}
.severity-negligible {
    color:#009E73;
}
.severity-negligible:after {
    font-family: Helvetica,arial,freesans,clean,sans-serif ;
    content: "n";
    font-weight:900;
    font-size:.6em;
    font-variant:small-caps;
    display:flex;
}
.severity-unrated {
}
.priority-urgent {
    color:#CC79A7;
}
.priority-high {
    color:#D55E00;
}
.priority-normal {
}
.priority-low {
    color:#0072B2;
}
src/test/java/com/gitblit/tests/GitBlitSuite.java
@@ -65,7 +65,7 @@
        FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class,
        ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class,
        BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class,
        SshKeysDispatcherTest.class })
        SshKeysDispatcherTest.class, UITicketTest.class })
public class GitBlitSuite {
    public static final File BASEFOLDER = new File("data");
src/test/java/com/gitblit/tests/TicketServiceTest.java
@@ -293,9 +293,47 @@
            assertTrue("failed to delete label " + label.name, service.deleteLabel(getRepository(), label.name, "lucifer"));
        }
    }
    @Test
    public void testPriorityAndSeverity() throws Exception {
        // C1: create and insert a ticket
        Change c1 = newChange("testPriorityAndSeverity() " + Long.toHexString(System.currentTimeMillis()));
        TicketModel ticket = service.createTicket(getRepository(), c1);
        assertTrue(ticket.number > 0);
        assertEquals(TicketModel.Priority.Normal, ticket.priority);
        assertEquals(TicketModel.Severity.Unrated, ticket.severity);
        TicketModel constructed = service.getTicket(getRepository(), ticket.number);
        compare(ticket, constructed);
        // C2: Change Priority max
        Change c2 = new Change("C2");
        c2.setField(Field.priority, TicketModel.Priority.Urgent);
        constructed = service.updateTicket(getRepository(), ticket.number, c2);
        assertNotNull(constructed);
        assertEquals(2, constructed.changes.size());
        assertEquals(TicketModel.Priority.Urgent, constructed.priority);
        assertEquals(TicketModel.Severity.Unrated, constructed.severity);
        // C3: Change Severity max
        Change c3 = new Change("C3");
        c3.setField(Field.severity, TicketModel.Severity.Catastrophic);
        constructed = service.updateTicket(getRepository(), ticket.number, c3);
        assertNotNull(constructed);
        assertEquals(3, constructed.changes.size());
        assertEquals(TicketModel.Priority.Urgent, constructed.priority);
        assertEquals(TicketModel.Severity.Catastrophic, constructed.severity);
        // C4: Change Priority min
        Change c4 = new Change("C3");
        c4.setField(Field.priority, TicketModel.Priority.Low);
        constructed = service.updateTicket(getRepository(), ticket.number, c4);
        assertNotNull(constructed);
        assertEquals(4, constructed.changes.size());
        assertEquals(TicketModel.Priority.Low, constructed.priority);
        assertEquals(TicketModel.Severity.Catastrophic, constructed.severity);
    }
    private Change newChange(String summary) {
        Change change = new Change("C1");
        change.setField(Field.title, summary);
src/test/java/com/gitblit/tests/UITicketTest.java
New file
@@ -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;
    }
}