package hudson.plugins.analysis.core;
import javax.annotation.CheckForNull;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import com.google.common.collect.Lists;
import jenkins.model.Jenkins;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Job;
import hudson.model.Action;
import hudson.model.Api;
import hudson.model.Run;
import hudson.plugins.analysis.graph.BuildResultGraph;
import hudson.plugins.analysis.graph.DefaultGraphConfigurationView;
import hudson.plugins.analysis.graph.DifferenceGraph;
import hudson.plugins.analysis.graph.EmptyGraph;
import hudson.plugins.analysis.graph.GraphConfiguration;
import hudson.plugins.analysis.graph.GraphConfigurationView;
import hudson.plugins.analysis.graph.HealthGraph;
import hudson.plugins.analysis.graph.NewVersusFixedGraph;
import hudson.plugins.analysis.graph.NullGraph;
import hudson.plugins.analysis.graph.PriorityGraph;
import hudson.plugins.analysis.graph.TotalsGraph;
import hudson.plugins.analysis.graph.TrendDetails;
import hudson.plugins.analysis.graph.UserGraphConfigurationView;
import hudson.util.Graph;
/**
* A job action displays a link on the side panel of a job. This action
* also is responsible to render the historical trend via its associated
* 'floatingBox.jelly' view.
*
* @param <T>
* result action type
* @author Ulli Hafner
*/
// CHECKSTYLE:COUPLING-OFF
@ExportedBean
public abstract class AbstractProjectAction<T extends ResultAction<?>> implements Action {
private static final Logger LOGGER = Logger.getLogger(AbstractProjectAction.class.getName());
/** Project that owns this action. */
private final Job<?, ?> owner;
/** The type of the result action. */
private final Class<? extends T> resultActionType;
/** The icon URL of this action: it will be shown as soon as a result is available. */
private final String iconUrl;
/** Plug-in URL. */
private final String pluginUrl;
/** Plug-in results URL. */
private final String resultUrl;
/** Human readable name of this action. */
private final Localizable name;
/** Human readable title of the trend graph. */
private final Localizable trendName;
/**
* Creates a new instance of {@link AbstractProjectAction}.
*
* @param job
* the job that owns this action
* @param resultActionType
* the type of the result action
* @param name
* the human readable name of this action
* @param trendName
* the human readable name of the trend graph
* @param pluginUrl
* the URL of the associated plug-in
* @param iconUrl
* the icon to show
* @param resultUrl
* the URL of the associated build results
*/
public AbstractProjectAction(final Job<?, ?> job, final Class<? extends T> resultActionType,
final Localizable name, final Localizable trendName, final String pluginUrl, final String iconUrl, final String resultUrl) {
this.owner = job;
this.resultActionType = resultActionType;
this.name = name;
this.trendName = trendName;
this.pluginUrl = pluginUrl;
this.iconUrl = iconUrl;
this.resultUrl = resultUrl;
}
/**
* Creates a new instance of {@link AbstractProjectAction}.
*
* @param project
* the project that owns this action
* @param resultActionType
* the type of the result action
* @param name
* the human readable name of this action
* @param trendName
* the human readable name of the trend graph
* @param pluginUrl
* the URL of the associated plug-in
* @param iconUrl
* the icon to show
* @param resultUrl
* the URL of the associated build results
* @deprecated use
* {@link #AbstractProjectAction(Job, Class, Localizable, Localizable, String, String, String)}
*/
@Deprecated
public AbstractProjectAction(final AbstractProject<?, ?> project, final Class<? extends T> resultActionType,
final Localizable name, final Localizable trendName, final String pluginUrl, final String iconUrl, final String resultUrl) {
this((Job<?, ?>) project, resultActionType, name, trendName, pluginUrl, iconUrl, resultUrl);
}
/**
* Gets the remote API for this action.
*
* @return the remote API
* @since 1.69
*/
public Api getApi() {
return new Api(this);
}
@Override @Exported
public String getDisplayName() {
return asString(name);
}
private String asString(final Localizable localizable) {
if (localizable == null) {
return null;
}
else {
return localizable.toString();
}
}
/**
* Returns the title of the trend graph.
*
* @return the title of the trend graph.
*/
public String getTrendName() {
return asString(trendName);
}
/**
* Returns the owner this action belongs to.
*
* @return the owner
*/
public final Job<?, ?> getOwner() {
return owner;
}
/**
* Returns the project this action belongs to.
*
* @return the project
*
* @deprecated use
* {@link #getOwner()}
*/
@Deprecated
public final AbstractProject<?, ?> getProject() {
return owner instanceof AbstractProject ? (AbstractProject<?, ?>) owner : null;
}
/**
* Returns the graph configuration view for this owner. If the requested
* link is neither the user graph configuration nor the default
* configuration then <code>null</code> is returned.
*
* @param link
* the requested link
* @param request
* Stapler request
* @param response
* Stapler response
* @return the dynamic result of the analysis (detail page).
*/
public Object getDynamic(final String link, final StaplerRequest request, final StaplerResponse response) {
if ("configureDefaults".equals(link)) {
return createDefaultConfiguration();
}
else if ("configure".equals(link)) {
return createUserConfiguration(request);
}
else {
return null;
}
}
/**
* Returns the trend graph details.
*
* @return the details
*/
public Object getTrendDetails() {
return getTrendDetails(Stapler.getCurrentRequest(), Stapler.getCurrentResponse());
}
/**
* Returns the trend graph details.
*
* @param request
* Stapler request
* @param response
* Stapler response
* @return the details
*/
public Object getTrendDetails(final StaplerRequest request, final StaplerResponse response) {
return new TrendDetails(getOwner(), getTrendGraph(request, response, "../../"), getTrendGraphId());
}
/**
* Returns the trend graph.
*
* @return the current trend graph
*/
public Object getTrendGraph() {
return getTrendGraph(Stapler.getCurrentRequest(), Stapler.getCurrentResponse());
}
/**
* Returns the configured trend graph.
*
* @param request
* Stapler request
* @param response
* Stapler response
* @return the trend graph
*/
public Graph getTrendGraph(final StaplerRequest request, final StaplerResponse response) {
return getTrendGraph(request, response, "");
}
private Graph getTrendGraph(final StaplerRequest request, final StaplerResponse response, final String urlPrefix) {
GraphConfigurationView configuration = createUserConfiguration(request);
if (configuration.hasMeaningfulGraph()) {
configuration.setUrlPrefix(urlPrefix);
return configuration.getGraphRenderer(getUrlName());
}
else {
BuildResultGraph graphType = configuration.getGraphType();
try {
response.sendRedirect2(request.getContextPath() + graphType.getExampleImage());
}
catch (IOException exception) {
LOGGER.log(Level.SEVERE, "Can't create graph: " + graphType, exception);
}
return null;
}
}
/**
* Returns whether the trend graph is visible.
*
* @param request
* the request to get the cookie from
* @return <code>true</code> if the trend is visible
*/
public boolean isTrendVisible(final StaplerRequest request) {
GraphConfigurationView configuration = createUserConfiguration(request);
return configuration.isVisible() && configuration.hasMeaningfulGraph();
}
/**
* Returns the ID of the selected trend graph.
*
* @return ID of the selected trend graph
*/
public String getTrendGraphId() {
GraphConfigurationView configuration = createUserConfiguration(Stapler.getCurrentRequest());
return configuration.getGraphType().getId();
}
/**
* Returns whether the trend graph is deactivated.
*
* @param request
* the request to get the cookie from
* @return <code>true</code> if the trend is deactivated
*/
public boolean isTrendDeactivated(final StaplerRequest request) {
return createUserConfiguration(request).isDeactivated();
}
/**
* Returns whether the enable trend graph link should be shown.
*
* @param request
* the request to get the cookie from
* @return the graph configuration
*/
public boolean canShowEnableTrendLink(final StaplerRequest request) {
GraphConfigurationView configuration = createUserConfiguration(request);
if (configuration.hasMeaningfulGraph()) {
return !configuration.isDeactivated() && !configuration.isVisible();
}
return false;
}
/**
* Creates a view to configure the trend graph for the current user.
*
* @param request
* Stapler request
* @return a view to configure the trend graph for the current user
*/
protected GraphConfigurationView createUserConfiguration(final StaplerRequest request) {
return new UserGraphConfigurationView(createConfiguration(), getOwner(),
getUrlName(), request.getCookies(), createBuildHistory());
}
/**
* Creates a view to configure the trend graph defaults.
*
* @return a view to configure the trend graph defaults
*/
protected GraphConfigurationView createDefaultConfiguration() {
return new DefaultGraphConfigurationView(createConfiguration(), getOwner(),
getUrlName(), createBuildHistory());
}
/**
* Creates the build history.
*
* @return build history
*/
protected BuildHistory createBuildHistory() {
Run<?, ?> lastFinishedRun = getLastFinishedRun();
if (lastFinishedRun == null) {
return new NullBuildHistory();
}
else {
return new BuildHistory(lastFinishedRun, resultActionType, false, false);
}
}
/**
* Creates the graph configuration.
*
* @return the graph configuration
*/
private GraphConfiguration createConfiguration() {
return createConfiguration(getAvailableGraphs());
}
/**
* Returns the sorted list of available graphs.
*
* @return the available graphs
*/
@SuppressWarnings("NP")
protected List<BuildResultGraph> getAvailableGraphs() {
List<BuildResultGraph> availableGraphs = Lists.newArrayList();
availableGraphs.add(new NewVersusFixedGraph());
availableGraphs.add(new PriorityGraph());
availableGraphs.add(new TotalsGraph());
if (hasValidResults()) {
availableGraphs.add(new HealthGraph(getLastAction().getHealthDescriptor()));
}
else {
availableGraphs.add(new HealthGraph(new NullHealthDescriptor()));
}
availableGraphs.add(new DifferenceGraph());
availableGraphs.add(new EmptyGraph());
availableGraphs.add(new NullGraph());
return availableGraphs;
}
/**
* Creates the graph configuration.
*
* @param availableGraphs
* the available graphs
* @return the graph configuration.
*/
protected GraphConfiguration createConfiguration(final List<BuildResultGraph> availableGraphs) {
return new GraphConfiguration(availableGraphs);
}
/**
* Returns the icon URL for the side-panel in the owner screen. If there
* is no valid result yet, then <code>null</code> is returned.
*
* @return the icon URL for the side-panel in the owner screen
*/
@Override
public String getIconFileName() {
ResultAction<?> lastAction = getLastAction();
if (lastAction != null && lastAction.getResult().hasAnnotations()) {
return Jenkins.RESOURCE_PATH + iconUrl;
}
return null;
}
@Override
public final String getUrlName() {
return pluginUrl;
}
/**
* Returns whether this owner has a valid result action attached.
*
* @return <code>true</code> if the results are valid
*/
public final boolean hasValidResults() {
return getLastAction() != null;
}
/**
* Returns the last valid result action.
*
* @return the last valid result action, or <code>null</code> if no such
* action is found
*/
@CheckForNull
public ResultAction<?> getLastAction() {
Run<?, ?> lastRun = getLastFinishedRun();
if (lastRun == null) {
return null;
}
else {
return getResultAction(lastRun);
}
}
/**
* Returns the result action for the specified build.
*
* @param lastRun
* the build to get the action for
* @return the action or <code>null</code> if there is no such action
*/
@CheckForNull
protected T getResultAction(final Run<?, ?> lastRun) {
return lastRun.getAction(resultActionType);
}
/**
* Returns the last finished run.
*
* @return the last finished run or <code>null</code> if there is no
* such run
*/
@CheckForNull @Exported
public Run<?, ?> getLastFinishedRun() {
if (owner == null) {
return null;
}
Run<?, ?> lastRun = owner.getLastBuild();
while (lastRun != null && (lastRun.isBuilding() || getResultAction(lastRun) == null)) {
lastRun = lastRun.getPreviousBuild();
}
return lastRun;
}
/**
* Returns the last finished build.
*
* @return the last finished build or <code>null</code> if there is no
* such build
* @deprecated use
* {@link #getLastFinishedRun()}
*/
@Deprecated @CheckForNull @Exported
public AbstractBuild<?, ?> getLastFinishedBuild() {
Run<?, ?> lastFinishedRun = getLastFinishedRun();
return lastFinishedRun instanceof AbstractBuild ? (AbstractBuild<?, ?>) lastFinishedRun : null;
}
/**
* Redirects the index page to the last result.
*
* @param request
* Stapler request
* @param response
* Stapler response
* @throws IOException
* in case of an error
*/
public void doIndex(final StaplerRequest request, final StaplerResponse response) throws IOException {
Run<?, ?> lastRun = getLastFinishedRun();
if (lastRun != null) {
response.sendRedirect2(String.format("../%d/%s", lastRun.getNumber(), resultUrl));
}
}
/**
* Creates a new instance of {@link AbstractProjectAction}.
*
* @param project
* the project that owns this action
* @param resultActionType
* the type of the result action
* @param plugin
* the plug-in that owns this action
* @deprecated use
* {@link #AbstractProjectAction(Job, Class, Localizable, Localizable, String, String, String)}
*/
@Deprecated
public AbstractProjectAction(final AbstractProject<?, ?> project, final Class<? extends T> resultActionType, final PluginDescriptor plugin) {
this((Job<?, ?>) project, resultActionType, null, null, plugin.getPluginName(), plugin.getIconUrl(), plugin.getPluginResultUrlName());
}
}