/*
* HeadsUp Agile
* Copyright 2009-2012 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.ci;
import org.apache.wicket.Component;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.list.ListView;
import org.headsupdev.agile.api.PropertyTree;
import org.headsupdev.agile.app.ci.event.UploadApplicationEvent;
import org.headsupdev.agile.app.ci.permission.BuildForcePermission;
import org.headsupdev.agile.app.ci.permission.BuildListPermission;
import org.headsupdev.agile.web.HeadsUpPage;
import org.headsupdev.agile.web.HeadsUpSession;
import org.headsupdev.agile.web.DynamicMenuLink;
import org.headsupdev.agile.web.components.FormattedDateModel;
import org.headsupdev.agile.web.components.FormattedDurationModel;
import org.headsupdev.agile.web.components.StripedListView;
import org.headsupdev.agile.web.components.ProjectTreeListView;
import org.headsupdev.agile.security.permission.ProjectListPermission;
import org.headsupdev.agile.storage.StoredProject;
import org.headsupdev.agile.storage.HibernateStorage;
import org.headsupdev.agile.storage.ci.Build;
import org.headsupdev.agile.api.Project;
import org.headsupdev.agile.api.Manager;
import org.headsupdev.agile.api.Permission;
import org.apache.wicket.markup.html.CSSPackageResource;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.PageParameters;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.model.Model;
import org.hibernate.Session;
import org.hibernate.Transaction;
import java.util.*;
/**
* Continuous integration home page
*
* @author Andrew Williams
* @version $Id$
* @since 1.0
*/
public class CI
extends HeadsUpPage
{
public void layout() {
super.layout();
add( CSSPackageResource.getHeaderContribution( CI.class, "ci.css" ) );
final boolean projectList = getProject().equals( StoredProject.getDefault() );
if ( projectList )
{
requirePermission( new ProjectListPermission() );
}
renderTopLinks( projectList );
WebMarkupContainer builds = new WebMarkupContainer( "buildlist" );
if ( projectList )
{
builds.setVisible( false );
}
else
{
builds.add( new StripedListView<Build>( "builds", CIApplication.getBuildsForProject( getProject() ) )
{
protected void populateItem( ListItem<Build> listItem )
{
super.populateItem( listItem );
Build build = listItem.getModelObject();
renderBuild( build, build.getProject(), false, false, listItem );
}
} );
}
add( builds );
add( createDownloadButton() );
WebMarkupContainer projects = new WebMarkupContainer( "projectlist" );
projects.add( new ProjectTreeListView( "projects", getProject() )
{
protected void populateProjectItem( ListItem listItem, Project project )
{
renderBuild( CIApplication.getLatestBuildForProject( project ), project,
true, CIBuilder.isProjectQueued( project ), listItem );
}
} );
add( projects.setVisible( ( projectList || getProject().getChildProjects().size() > 0 ) &&
Manager.getSecurityInstance().userHasPermission( getSession().getUser(), new ProjectListPermission(),
getProject() ) ) );
}
@Override
public String getTitle()
{
return "Builds for project " + getProject().getAlias();
}
protected Component createDownloadButton()
{
WebMarkupContainer button = new WebMarkupContainer( "download" );
if ( getProject().equals( StoredProject.getDefault() ) )
{
return button.setVisible( false );
}
UploadApplicationEvent upload = CIApplication.getLatestUploadEvent( getProject() );
if ( upload == null )
{
return button.setVisible( false );
}
ExternalLink link = new ExternalLink( "link", upload.getLink() );
link.add( new Label( "label", getButtonLabel( upload ) ) );
button.add( link );
return button;
}
protected String getButtonLabel( UploadApplicationEvent upload )
{
return "Download (build " + upload.getBuildNumber() + ")";
}
void renderTopLinks( boolean projectList )
{
if ( !projectList )
{
Build latest = CIApplication.getLatestBuildForProject( getProject() );
int status = Build.BUILD_SUCCEEDED;
if ( latest != null )
{
status = latest.getStatus();
}
WebMarkupContainer building = new WebMarkupContainer( "building" );
WebMarkupContainer queued = new WebMarkupContainer( "queued" );
add( building.setVisible( status == Build.BUILD_RUNNING ) );
add( queued.setVisible( CIBuilder.isProjectQueued( getProject() ) ) );
if ( userHasPermission( ( (HeadsUpSession) getPage().getSession() ).getUser(),
new BuildForcePermission(), getProject() ) && CIApplication.getHandlerFactory().supportsBuilding( getProject() ) )
{
if ( status != Build.BUILD_RUNNING && !CIBuilder.isProjectQueued( getProject() ) )
{
if ( isDefaultScheduleEnabled() )
{
addLink(new DynamicMenuLink( "build" )
{
public void onClick()
{
buildProject(getProject());
}
});
}
final PropertyTree buildConfigs = Manager.getStorageInstance().getGlobalConfiguration().
getApplicationConfigurationForProject( CIApplication.ID, getProject() ).getSubTree( "schedule" );
List<String> configNames = new LinkedList<String>( buildConfigs.getSubTreeIds() );
if ( configNames.size() > 1 )
{
configNames.remove( "default" );
Collections.sort( configNames );
configNames.add( 0, "default" );
}
add( new ListView<String>( "buildConfigs", configNames )
{
@Override
protected void populateItem( ListItem<String> listItem )
{
final String configId = listItem.getModelObject();
final PropertyTree config = buildConfigs.getSubTree( configId );
String configName = config.getProperty( "name" );
if ( CIBuilder.isBuildConfigDisabled(config) )
{
listItem.setVisible( false );
return;
}
if ( configName == null )
{
if ( !configId.equals( "default" ) )
{
listItem.setVisible( false );
return;
}
configName = "project";
}
else
{
configName = "\"" + configName + "\"";
}
Link build = new Link( "build" )
{
@Override
public void onClick()
{
buildProject( getProject(), configId, config );
}
};
build.add( new Label( "name", configName ) );
listItem.add( build );
}
} );
}
else
{
add( new WebMarkupContainer( "buildConfigs" ).setVisible( false ) );
}
if ( status == Build.BUILD_RUNNING )
{
building.add( new Link( "cancel" )
{
public void onClick()
{
cancelBuild( getProject() );
}
} );
addLink( new DynamicMenuLink( "cancel" )
{
public void onClick()
{
cancelBuild( getProject() );
}
} );
}
else
{
add( new WebMarkupContainer( "cancel" ).setVisible( false ) );
}
if ( CIBuilder.isProjectQueued( getProject() ) )
{
queued.add( new Link( "remove" )
{
public void onClick()
{
dequeueProject( getProject() );
}
} );
addLink( new DynamicMenuLink( "dequeue" )
{
public void onClick()
{
dequeueProject( getProject() );
}
} );
}
else
{
add( new WebMarkupContainer( "remove" ).setVisible( false ) );
}
}
else
{
add( new WebMarkupContainer( "build" ).setVisible( false ) );
add( new WebMarkupContainer( "buildConfigs" ).setVisible( false ) );
building.add( new WebMarkupContainer( "cancel" ).setVisible( false ) );
queued.add( new WebMarkupContainer( "remove" ).setVisible( false ) );
}
}
else
{
add( new WebMarkupContainer( "building" ).setVisible( false ) );
add( new WebMarkupContainer( "queued" ).setVisible( false ) );
add( new WebMarkupContainer( "build" ).setVisible( false ) );
add( new WebMarkupContainer( "buildConfigs" ).setVisible( false ) );
}
}
private boolean isDefaultScheduleEnabled()
{
if ( !CIApplication.getHandlerFactory().supportsBuilding( getProject() ) )
{
return false;
}
final PropertyTree buildConfigs = Manager.getStorageInstance().getGlobalConfiguration().
getApplicationConfigurationForProject( CIApplication.ID, getProject() ).getSubTree( "schedule" );
PropertyTree defaultConfig = buildConfigs.getSubTree( "default" );
if ( defaultConfig == null )
{
return !(Boolean) CIApplication.CONFIGURATION_BUILD_DISABLED.getDefault();
}
return !CIBuilder.isBuildConfigDisabled(defaultConfig);
}
private void renderBuild( final Build build, final Project project, final boolean projectList,
final boolean queued, final ListItem listItem )
{
if ( projectList ) {
PageParameters params = new PageParameters();
params.add( "project", project.getId() );
Link link = new BookmarkablePageLink<CI>( "project-link", getClass(), params );
link.add( new Label( "name", project.getAlias() ) );
listItem.add( link );
}
else
{
listItem.add( new Label( "name", project.getAlias() ) );
}
if ( build == null || build.getConfigName() == null )
{
listItem.add( new Label( "config", "" ) );
}
else
{
listItem.add( new Label( "config", build.getConfigName() ) );
}
final boolean canForce = projectList && userHasPermission( ( (HeadsUpSession) getPage().getSession() ).getUser(),
new BuildForcePermission(), project ) && CIApplication.getHandlerFactory().supportsBuilding( project );
WebMarkupContainer statusLink = new Link( "status-link" )
{
public void onClick()
{
if ( queued )
{
dequeueProject( project );
}
else if ( build != null && build.getStatus() == Build.BUILD_RUNNING )
{
cancelBuild( project );
}
else
{
buildProject( project );
}
}
};
if ( build != null )
{
WebMarkupContainer status;
if ( canForce )
{
status = statusLink;
listItem.add( new WebMarkupContainer( "status-icon" ).setVisible( false ) );
}
else
{
status = new WebMarkupContainer( "status-icon" );
listItem.add( statusLink.setVisible( false ) );
}
status.add( new AttributeModifier( "class", new Model<String>()
{
public String getObject()
{
if ( queued )
{
if ( canForce )
{
return "status-queued status-remove";
}
return "status-queued";
}
else
{
switch ( build.getStatus() )
{
case Build.BUILD_SUCCEEDED:
if ( canForce )
{
return "status-passed status-force";
}
return "status-passed";
case Build.BUILD_FAILED:
case Build.BUILD_CANCELLED:
if ( canForce )
{
return "status-failed status-force";
}
return "status-failed";
default:
if ( canForce )
{
return "status-running status-remove";
}
return "status-running";
}
}
}
} ) );
listItem.add( status );
listItem.add( new Label( "start", new FormattedDateModel( build.getStartTime(),
getSession().getTimeZone() ) ) );
listItem.add( new Label( "duration", new FormattedDurationModel( build.getStartTime(),
build.getEndTime() )
{
public String getObject() {
if ( build.getEndTime() == null )
{
return super.getObject() + "...";
}
return super.getObject();
}
} ) );
listItem.add( new Label( "tests", String.valueOf( build.getTests() ) )
.add( new CITestStatusModifier( "tests", build, "tests" ) ) );
listItem.add( new Label( "failures", String.valueOf( build.getFailures() ) )
.add( new CITestStatusModifier( "failures", build, "failures" ) ) );
listItem.add( new Label( "errors", String.valueOf( build.getErrors() ) )
.add( new CITestStatusModifier( "errors", build, "errors" ) ) );
listItem.add( new Label( "warnings", String.valueOf( build.getWarnings() ) )
.add( new CITestStatusModifier( "warnings", build, "warnings" ) ) );
PageParameters params = new PageParameters();
params.add( "project", project.getId() );
params.add( "id", String.valueOf( build.getId() ) );
BookmarkablePageLink link = new BookmarkablePageLink<View>( "buildId-link", View.class, params );
link.add( new Label( "buildId-label", String.valueOf( build.getId() ) ) );
listItem.add( link );
}
else
{
statusLink.add( new AttributeModifier( "class", new Model<String>()
{
public String getObject()
{
if ( queued )
{
return "status-queued status-force";
}
return "status-force";
}
} ) );
WebMarkupContainer statusIcon = new WebMarkupContainer( "status-icon" );
statusIcon.add( new AttributeModifier( "class", new Model<String>()
{
public String getObject()
{
if ( queued )
{
return "status-queued";
}
return "";
}
} ) );
listItem.add( statusIcon.setVisible( !canForce ) );
listItem.add( statusLink.setVisible( canForce ) );
listItem.add( new Label( "start", "" ) );
listItem.add( new Label( "duration", "" ) );
listItem.add( new Label( "tests", "" ) );
listItem.add( new Label( "failures", "" ) );
listItem.add( new Label( "errors", "" ) );
listItem.add( new Label( "warnings", "" ) );
listItem.add( new WebMarkupContainer( "buildId-link" ).setVisible( false ) );
}
}
protected void buildProject( Project project )
{
Manager.getInstance().getScmService().updateProject( project );
CIApplication.getBuilder().buildProject( project, false );
}
protected void buildProject( Project project, String id, PropertyTree config )
{
Manager.getInstance().getScmService().updateProject( project );
CIApplication.getBuilder().buildProject( project, id, config, false );
}
protected void dequeueProject( Project project )
{
CIApplication.getBuilder().dequeueProject( project );
}
protected void cancelBuild( Project project )
{
Build b = CIApplication.getLatestBuildForProject( project );
if ( b.getEndTime() == null )
{
b.setEndTime( new Date() );
b.setStatus( Build.BUILD_CANCELLED );
saveBuild( b );
// TODO actually cancel a build - if possible
}
}
public Permission getRequiredPermission() {
return new BuildListPermission();
}
private void saveBuild( Build build )
{
HibernateStorage storage = (HibernateStorage) Manager.getStorageInstance();
Session session = storage.getHibernateSession();
Transaction tx = session.beginTransaction();
session.update( build );
tx.commit();
storage.closeSession();
}
}