/* * HeadsUp Agile * Copyright 2009-2014 Heads Up Development Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.headsupdev.agile.app.issues; import org.apache.wicket.PageParameters; import org.apache.wicket.ResourceReference; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.image.Image; import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.markup.html.panel.Panel; import org.headsupdev.agile.api.Manager; import org.headsupdev.agile.api.Project; import org.headsupdev.agile.api.User; import org.headsupdev.agile.api.service.ChangeSet; import org.headsupdev.agile.app.issues.event.UpdateIssueEvent; import org.headsupdev.agile.storage.HibernateStorage; import org.headsupdev.agile.storage.StoredProject; import org.headsupdev.agile.storage.issues.Issue; import org.headsupdev.agile.storage.issues.IssueRelationship; import org.headsupdev.agile.storage.issues.Milestone; import org.headsupdev.agile.web.ApplicationPageMapper; import org.headsupdev.agile.web.HeadsUpPage; import org.headsupdev.agile.web.HeadsUpSession; import org.headsupdev.agile.web.RenderUtil; import org.headsupdev.agile.web.components.*; import org.headsupdev.agile.web.components.issues.*; import org.hibernate.Session; import org.hibernate.Transaction; import java.util.*; /** * A panel which shows the details of an issue with icons and links to milestones etc * * @author Andrew Williams * @since 1.0 */ public class IssuePanel extends Panel { private final ListView<User> watchers; public IssuePanel( String id, final Issue issue ) { super( id ); add( new Label( "id", String.valueOf( issue.getId() ) ) ); add( new Label( "project", ( issue.getProject() == null ) ? "" : issue.getProject().toString() ) ); WebMarkupContainer type = new WebMarkupContainer( "type" ); type.add( new Label( "typelabel", IssueUtils.getTypeName( issue.getType() ) ) ); String typeIcon = "type/" + IssueUtils.getTypeName( issue.getType() ) + ".png"; type.add( new Image( "type-icon", new ResourceReference( IssueListPanel.class, typeIcon ) ) ); add( type ); add( new Label( "priority", IssueUtils.getPriorityName( issue.getPriority() ) ) ); add( new Label( "version", issue.getVersion() ) ); final Milestone milestone = issue.getMilestone(); Class userPage = RenderUtil.getPageClass( "account" ); Class milestonePage = RenderUtil.getPageClass( "milestones/view" ); PageParameters params = new PageParameters(); if ( milestone == null ) { add( new WebMarkupContainer( "milestone-link" ).setVisible( false ) ); add( new Form( "add-form" ).setVisible( false ) ); } else { params.add( "project", milestone.getProject().getId() ); params.add( "id", milestone.getName() ); Link milestoneLink = new BookmarkablePageLink( "milestone-link", milestonePage, params ); milestoneLink.add( new Label( "milestone", milestone.toString() ) ); add( milestoneLink ); OnePressSubmitButton addIssue = new OnePressSubmitButton( "milestone-add-issue" ) { @Override public void onSubmit() { super.onSubmit(); PageParameters addIssueParams = new PageParameters(); addIssueParams.add( "project", milestone.getProject().getId() ); addIssueParams.add( "milestone", milestone.getName() ); setResponsePage( RenderUtil.getPageClass( "issues/create" ), addIssueParams ); } }; add( new Form( "add-form" ).add( addIssue.setDefaultFormProcessing( false ) ) ); } String image = null; switch ( issue.getPriority() ) { case Issue.PRIORITY_BLOCKER: image = "blocker.png"; break; case Issue.PRIORITY_CRITICAL: image = "critical.png"; break; case Issue.PRIORITY_MINOR: image = "minor.png"; break; case Issue.PRIORITY_TRIVIAL: image = "trivial.png"; break; } if ( image == null ) { WebMarkupContainer ico = new WebMarkupContainer( "priority-icon" ); ico.setVisible( false ); add( ico ); } else { add( new Image( "priority-icon", new ResourceReference( IssueListPanel.class, image ) ) ); } add( new Label( "order", issue.getOrder() == null ? "" : String.valueOf( issue.getOrder() ) ). setVisible( issue.getOrder() != null ) ); watchers = new ListView<User>( "watchers", new ArrayList<User>( issue.getWatchers() ) ) { @Override protected void populateItem( ListItem<User> listItem ) { User user = listItem.getModelObject(); GravatarLinkPanel gravatarLinkPanel = new GravatarLinkPanel( "gravatar", user, HeadsUpPage.DEFAULT_AVATAR_EDGE_LENGTH ); listItem.add( gravatarLinkPanel.setVisible( user != null ) ); } }; add( watchers ); // TODO move the relationship comparators out of here, they are basically the same but opposite end of relationship compared List<IssueRelationship> relationships = new LinkedList<IssueRelationship>(); relationships.addAll( issue.getRelationships() ); Collections.sort( relationships, new Comparator<IssueRelationship>() { public int compare( IssueRelationship relationship1, IssueRelationship relationship2 ) { if ( relationship1.getType() == relationship2.getType() ) { if ( relationship1.getRelated().getId() == relationship2.getRelated().getId() ) { if ( relationship1.getRelated().getProject().equals( issue.getProject() ) ) { return Integer.MIN_VALUE; } else if ( relationship2.getRelated().getProject().equals( issue.getProject() ) ) { return Integer.MAX_VALUE; } return relationship1.getRelated().getProject().getId().compareToIgnoreCase( relationship2.getRelated().getProject().getId() ); } return (int) ( relationship1.getRelated().getId() - relationship2.getRelated().getId() ); } return relationship1.getType() - relationship2.getType(); } } ); add( new ListView<IssueRelationship>( "relationship", relationships ) { protected void populateItem( ListItem<IssueRelationship> listItem ) { IssueRelationship relationship = listItem.getModelObject(); listItem.add( new Label( "name", IssueUtils.getRelationshipName( relationship.getType() ) ) ); Issue related = relationship.getRelated(); PageParameters params = new PageParameters(); params.put( "project", related.getProject() ); params.put( "id", related.getId() ); BookmarkablePageLink link = new BookmarkablePageLink( "issue-link", ViewIssue.class, params ); String issueId = String.valueOf( related.getId() ); if ( !related.getProject().equals( issue.getProject() ) ) { issueId = related.getProject().getId() + ":" + related.getId(); } link.add( new Label( "issue", "issue:" + issueId ) ); listItem.add( link.add( new IssueStatusModifier( "relatedstatus", related ) ) ); listItem.add( new Label( "summary", related.getSummary() ) .add( new IssueStatusModifier( "relatedstatus", related ) ) ); } } ); relationships = new LinkedList<IssueRelationship>(); relationships.clear(); relationships.addAll( issue.getReverseRelationships() ); Collections.sort( relationships, new Comparator<IssueRelationship>() { public int compare( IssueRelationship relationship1, IssueRelationship relationship2 ) { if ( relationship1.getType() == relationship2.getType() ) { if ( relationship1.getOwner().getId() == relationship2.getOwner().getId() ) { if ( relationship1.getOwner().getProject().equals( issue.getProject() ) ) { return Integer.MIN_VALUE; } else if ( relationship2.getOwner().getProject().equals( issue.getProject() ) ) { return Integer.MAX_VALUE; } return relationship1.getOwner().getProject().getId().compareToIgnoreCase( relationship2.getOwner().getProject().getId() ); } return (int) ( relationship1.getOwner().getId() - relationship2.getOwner().getId() ); } return relationship1.getType() - relationship2.getType(); } } ); add( new ListView<IssueRelationship>( "reverse-relationship", relationships ) { protected void populateItem( ListItem<IssueRelationship> listItem ) { IssueRelationship relationship = listItem.getModelObject(); listItem.add( new Label( "name", IssueUtils.getReverseRelationshipName( relationship.getType() ) ) ); Issue related = relationship.getOwner(); PageParameters params = new PageParameters(); params.put( "project", related.getProject() ); params.put( "id", related.getId() ); BookmarkablePageLink link = new BookmarkablePageLink( "issue-link", ViewIssue.class, params ); String issueId = String.valueOf( related.getId() ); if ( !related.getProject().equals( issue.getProject() ) ) { issueId = related.getProject().getId() + ":" + related.getId(); } link.add( new Label( "issue", "issue:" + issueId ) ); listItem.add( link.add( new IssueStatusModifier( "relatedstatus", related ) ) ); listItem.add( new Label( "summary", related.getSummary() ) .add( new IssueStatusModifier( "relatedstatus", related ) ) ); } } ); add( new ListView<ChangeSet>( "changeset", new ArrayList<ChangeSet>( issue.getChangeSets() ) ) { protected void populateItem( ListItem<ChangeSet> listItem ) { ChangeSet change = listItem.getModelObject(); PageParameters params = new PageParameters(); params.put( "project", change.getProject() ); params.put( "id", change.getId() ); BookmarkablePageLink link = new BookmarkablePageLink( "set-link", ApplicationPageMapper.get().getPageClass( "files/change" ), params ); String changeId = String.valueOf( change.getId() ); if ( !change.getProject().equals( issue.getProject() ) ) { changeId = change.getProject().getId() + ":" + change.getId(); } link.add( new Label( "set", "change:" + changeId ) ); listItem.add( link ); listItem.add( new AccountFallbackLink( "user", change.getAuthor() ) ); listItem.add( new Label( "summary", MarkedUpTextModel.markUp( change.getComment(), change.getProject() ) ).setEscapeModelStrings( false ) ); } } ); add( new Label( "status", IssueUtils.getStatusDescription( issue ) ) ); if ( canSessionUserBeginIssue( issue ) ) { addBeginIssueButton( issue ); } else { add( new Form( "begin-issue-form" ).setVisible( false ) ); } GravatarLinkPanel gravatarLinkReporter = new GravatarLinkPanel( "gravatarReporter", issue.getReporter(), HeadsUpPage.DEFAULT_AVATAR_EDGE_LENGTH ); add( gravatarLinkReporter.setVisible( issue.getReporter() != null ) ); GravatarLinkPanel gravatarLinkAssignee = new GravatarLinkPanel( "gravatarAssignee", issue.getAssignee(), HeadsUpPage.DEFAULT_AVATAR_EDGE_LENGTH ); add( gravatarLinkAssignee.setVisible( issue.getAssignee() != null ) ); if ( isSessionUserAssignedToIssue( issue ) ) { addDropIssueButton( issue ); } else { add( new Form( "drop-issue-form" ).setVisible( false ) ); } add( new Label( "created", new FormattedDateModel( issue.getCreated(), ( (HeadsUpSession) getSession() ).getTimeZone() ) ) ); add( new Label( "updated", new FormattedDateModel( issue.getUpdated(), ( (HeadsUpSession) getSession() ).getTimeZone() ) ) ); boolean useTime = Boolean.parseBoolean( issue.getProject().getConfigurationValue( StoredProject.CONFIGURATION_TIMETRACKING_ENABLED ) ); add( new Label( "estimate", new IssueHoursEstimateModel( issue ) { @Override public String getObject() { String time = super.getObject(); if ( issue.getIncludeInInitialEstimates() ) { time += " (initial)"; } return time; } } ).setVisible( useTime ) ); add( new Label( "required", new IssueHoursRequiredModel( issue ) ).setVisible( useTime ) ); if ( Boolean.parseBoolean( issue.getProject().getConfigurationValue( StoredProject.CONFIGURATION_TIMETRACKING_BURNDOWN ) ) ) { add( new Label( "requiredLabel", "Remaining" ) ); } else { add( new Label( "requiredLabel", "Taken" ) ); } if ( ( (HibernateStorage) Manager.getStorageInstance() ).getResourceManager().isIssueMissingEstimate( issue ) ) { if ( ( (HibernateStorage) Manager.getStorageInstance() ).getResourceManager().isIssueSeriouslyMissingEstimate( issue ) ) { add( new Image( "overworked-icon", new ResourceReference( HeadsUpPage.class, "images/fail.png" ) ) ); } else { add( new Image( "overworked-icon", new ResourceReference( HeadsUpPage.class, "images/warn.png" ) ) ); } } else { add( new WebMarkupContainer( "overworked-icon" ).setVisible( false ) ); } add( new Label( "summary", issue.getSummary() ) ); add( new Label( "environment", issue.getEnvironment() ) ); add( new Label( "description", new MarkedUpTextModel( issue.getBody(), issue.getProject() ) ) .setEscapeModelStrings( false ) ); add( new Label( "testNotes", new MarkedUpTextModel( issue.getTestNotes(), issue.getProject() ) ) .setEscapeModelStrings( false ).setVisible( issue.getTestNotes() != null ) ); } private boolean isSessionUserAssignedToIssue( Issue issue ) { return issue.getAssignee() != null && issue.getAssignee().equals( getSessionUser() ); } private boolean canSessionUserBeginIssue( Issue issue ) { return issue.getStatus() < Issue.STATUS_INPROGRESS && !getSessionUser().equals( HeadsUpSession.ANONYMOUS_USER ); } private User getSessionUser() { return ( (HeadsUpSession) getSession() ).getUser(); } private void addDropIssueButton( final Issue issue ) { final Form form = new Form( "drop-issue-form" ); OnePressSubmitButton button = new OnePressSubmitButton( "drop-issue" ) { @Override public void onSubmit() { super.onSubmit(); issue.setAssignee( null ); issue.getWatchers().remove( getSessionUser() ); issue.setStatus( Issue.STATUS_NEW ); issue.setUpdated( new Date() ); Session session = ( (HibernateStorage) Manager.getStorageInstance() ).getHibernateSession(); Transaction tx = session.beginTransaction(); session.update( issue ); tx.commit(); setResponsePage( ViewIssue.class, getPage().getPageParameters() ); ApplicationPageMapper.get().getApplication( "issues" ).addEvent( new UpdateIssueEvent( issue, issue.getProject(), getSessionUser(), "dropped" ) ); } }; add( form.add( button.setDefaultFormProcessing( false ) ) ); } private void addBeginIssueButton( final Issue issue ) { final Form form = new Form( "begin-issue-form" ); OnePressSubmitButton button = new OnePressSubmitButton( "begin-issue" ) { @Override public void onSubmit() { super.onSubmit(); issue.setAssignee( getSessionUser() ); issue.getWatchers().add( getSessionUser() ); issue.setStatus( Issue.STATUS_INPROGRESS ); issue.setUpdated( new Date() ); Session session = ( (HibernateStorage) Manager.getStorageInstance() ).getHibernateSession(); Transaction tx = session.beginTransaction(); session.update( issue ); tx.commit(); setResponsePage( ViewIssue.class, getPage().getPageParameters() ); ApplicationPageMapper.get().getApplication( "issues" ).addEvent( new UpdateIssueEvent( issue, issue.getProject(), getSessionUser(), "began" ) ); } }; add( form.add( button.setDefaultFormProcessing( false ) ) ); } protected PageParameters getProjectPageParameters( Project project ) { PageParameters ret = new PageParameters(); ret.add( "project", project.getId() ); return ret; } }