/*
This file is part of Delivery Pipeline Plugin.
Delivery Pipeline Plugin is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Delivery Pipeline Plugin 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Delivery Pipeline Plugin.
If not, see <http://www.gnu.org/licenses/>.
*/
package se.diabol.jenkins.workflow.model;
import static java.lang.Math.round;
import static se.diabol.jenkins.workflow.util.Util.getRunById;
import com.cloudbees.workflow.flownode.FlowNodeUtil;
import hudson.model.Result;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.workflow.actions.NotExecutedNodeAction;
import org.jenkinsci.plugins.workflow.actions.TimingAction;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.kohsuke.stapler.export.Exported;
import se.diabol.jenkins.pipeline.domain.AbstractItem;
import se.diabol.jenkins.pipeline.domain.status.Status;
import se.diabol.jenkins.pipeline.domain.status.StatusFactory;
import se.diabol.jenkins.pipeline.domain.task.ManualStep;
import se.diabol.jenkins.workflow.WorkflowApi;
import se.diabol.jenkins.workflow.api.Run;
import se.diabol.jenkins.workflow.api.Stage;
import se.diabol.jenkins.workflow.step.TaskAction;
import se.diabol.jenkins.workflow.util.Name;
import se.diabol.jenkins.workflow.util.Util;
import java.util.ArrayList;
import java.util.List;
public class Task extends AbstractItem {
private static final WorkflowApi workflowApi = new WorkflowApi(Jenkins.getInstance());
private final String id;
private final String link;
private final Status status;
private final ManualStep manual;
private final String buildId;
private final String description;
public Task(String id, String name, Status status, String link, ManualStep manual, String description) {
super(name);
this.id = id;
this.link = link;
this.status = status;
this.manual = manual;
this.buildId = null;
this.description = description;
}
@Exported
public ManualStep getManualStep() {
return manual;
}
@Exported
public boolean isManual() {
return manual != null;
}
@Exported
public String getBuildId() {
return buildId;
}
@Exported
public String getId() {
return id;
}
@Exported
public String getLink() {
return link;
}
@Exported
public Status getStatus() {
return status;
}
@Exported
public String getDescription() {
return description;
}
public static List<Task> resolve(WorkflowRun build, FlowNode stageStartNode) {
List<Task> result = new ArrayList<Task>();
List<FlowNode> stageNodes = FlowNodeUtil.getStageNodes(stageStartNode);
List<FlowNode> taskNodes = Util.getTaskNodes(stageNodes);
if (taskNodesDefinedInStage(taskNodes)) {
for (FlowNode flowNode : taskNodes) {
TaskAction action = flowNode.getAction(TaskAction.class);
result.add(new Task(flowNode.getId(), action.getTaskName(), resolveTaskStatus(build, stageStartNode),
taskLinkFor(build), null, null));
}
} else {
Status stageStatus = resolveTaskStatus(build, stageStartNode);
result.add(createStageTask(build, stageStartNode, stageStatus));
}
return result;
}
private static Task createStageTask(WorkflowRun build, FlowNode stageStartNode, Status stageStatus) {
return new Task(stageStartNode.getId(), stageStartNode.getDisplayName(), stageStatus,
taskLinkFor(build), null, null);
}
private static String taskLinkFor(WorkflowRun build) {
return "job/" + Name.of(build);
}
static boolean taskNodesDefinedInStage(List<FlowNode> taskNodes) {
return !taskNodes.isEmpty();
}
private static Status resolveTaskStatus(WorkflowRun build, FlowNode stageStartNode) {
List<Run> runs = workflowApi.getRunsFor(Name.of(build));
Run run = getRunById(runs, build.getNumber());
se.diabol.jenkins.workflow.api.Stage currentStage = run.getStageByName(stageStartNode.getDisplayName());
if (currentStage == null) {
return resolveStatus(build, FlowNodeUtil.getStageNodes(stageStartNode), run.stages);
} else {
Status stageStatus = WorkflowStatus.of(currentStage);
if (stageStatus.isRunning()) {
stageStatus = runningStatus(build, currentStage);
}
return stageStatus;
}
}
private static Status resolveStatus(WorkflowRun build, List<FlowNode> taskNodes, List<Stage> stages) {
if (Result.FAILURE.equals(build.getResult())) {
return StatusFactory.failed(getStartTime(taskNodes), getDuration(stages), false, null);
}
if (isRunning(taskNodes) && !build.getExecution().isComplete()) {
return runningStatus(build);
}
if (allExecuted(taskNodes)) {
if (failed(Util.head(taskNodes))) {
return StatusFactory.failed(getStartTime(taskNodes), getDuration(stages), false, null);
} else {
return StatusFactory.success(getStartTime(taskNodes), getDuration(stages), false, null);
}
} else {
return StatusFactory.idle();
}
}
protected static boolean failed(FlowNode node) {
return node != null && node.getError() != null;
}
private static Status runningStatus(WorkflowRun build) {
long buildTimestamp = build.getTimeInMillis();
int progress = calculateProgress(buildTimestamp, build.getEstimatedDuration());
return runningStatus(buildTimestamp, progress);
}
private static Status runningStatus(WorkflowRun build, Stage stage) {
int progress = progressOfStage(build, stage);
return runningStatus(build.getTimeInMillis(), progress);
}
private static Status runningStatus(long buildTimestamp, int progress) {
if (progress > 100) {
progress = 99;
}
return StatusFactory.running(progress, buildTimestamp, System.currentTimeMillis() - buildTimestamp);
}
private static int progressOfStage(WorkflowRun build, Stage currentStage) {
Run previousRun = workflowApi.lastFinishedRunFor(Name.of(build));
if (!previousRun.hasStage(currentStage.name)) {
return 99;
}
long stageStartTime = currentStage.startTimeMillis.getValue();
long estimatedStageDuration = Stage.getDurationOfStageFromRun(previousRun, currentStage);
return calculateProgress(stageStartTime, estimatedStageDuration);
}
static int calculateProgress(long timestampFromBuild, long estimatedDuration) {
return (int) round(100.0d
* (System.currentTimeMillis() - timestampFromBuild)
/ estimatedDuration);
}
private static boolean allExecuted(List<FlowNode> nodes) {
for (FlowNode node : nodes) {
if (!NotExecutedNodeAction.isExecuted(node)) {
return false;
}
}
return true;
}
static boolean isRunning(List<FlowNode> nodes) {
if (nodes != null) {
for (FlowNode node : nodes) {
if (node.isRunning()) {
return true;
}
}
}
return false;
}
static long getStartTime(List<FlowNode> nodes) {
if (nodes != null && !nodes.isEmpty()) {
return TimingAction.getStartTime(nodes.get(0));
}
return 0;
}
protected static long getDuration(List<Stage> stages) {
long result = 0;
if (stages != null) {
for (Stage stage : stages) {
result = result + stage.durationMillis;
}
}
return result;
}
}