From 8ba2da1314303dc4f54548b64b09b104ceabd9a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tommy=20Tynj=C3=A4?= Date: Thu, 18 Oct 2018 16:12:49 +0200 Subject: [PATCH] JENKINS-54191 Persist task finished time --- .../diabol/jenkins/workflow/model/Task.java | 31 +++++-- .../workflow/step/TaskFinishedAction.java | 36 ++++++++ .../workflow/step/TaskStepExecution.java | 12 ++- .../se/diabol/jenkins/workflow/util/Util.java | 16 ++++ .../jenkins/workflow/util/UtilTest.java | 85 +++++++++++++++---- 5 files changed, 151 insertions(+), 29 deletions(-) create mode 100644 src/main/java/se/diabol/jenkins/workflow/step/TaskFinishedAction.java diff --git a/src/main/java/se/diabol/jenkins/workflow/model/Task.java b/src/main/java/se/diabol/jenkins/workflow/model/Task.java index b5814e048..ea63d1619 100644 --- a/src/main/java/se/diabol/jenkins/workflow/model/Task.java +++ b/src/main/java/se/diabol/jenkins/workflow/model/Task.java @@ -18,7 +18,9 @@ package se.diabol.jenkins.workflow.model; import static org.apache.commons.collections.CollectionUtils.isNotEmpty; +import static se.diabol.jenkins.workflow.util.Util.getParentNodeWithTaskFinishedAction; import static se.diabol.jenkins.workflow.util.Util.getRunById; +import static se.diabol.jenkins.workflow.util.Util.isAnyParentNodeContainingTaskFinishedAction; import com.cloudbees.workflow.flownode.FlowNodeUtil; import org.jenkinsci.plugins.workflow.actions.TimingAction; @@ -36,11 +38,13 @@ 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.step.TaskFinishedAction; import se.diabol.jenkins.workflow.util.Name; import se.diabol.jenkins.workflow.util.Util; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class Task extends AbstractItem { @@ -173,27 +177,40 @@ private static Status resolveTaskStatus(WorkflowRun build, FlowNode taskNode, TaskAction taskAction) throws PipelineException { Stage stage = getStage(build, stageStartNode); - - Long finishedTime = taskAction.getFinishedTime(); + Long finishedTime = getTaskFinishedTime(taskNode, taskAction); if (finishedTime != null) { - long duration = finishedTime - getTaskStartTime(taskNode); - return new SimpleStatus(StatusType.SUCCESS, finishedTime, duration); + long taskDuration = finishedTime - getTaskStartTime(taskNode); + return new SimpleStatus(StatusType.SUCCESS, finishedTime, taskDuration); } else { Status stageStatus = resolveStageStatus(build, stage); if (stageStatus.isRunning()) { return runningStatus(build, stage); } else { - long duration = (stage.startTimeMillis.getMillis() + stage.durationMillis) - getTaskStartTime(taskNode); - return new SimpleStatus(stageStatus.getType(), - stage.startTimeMillis.getMillis() + stage.durationMillis, duration); + long taskDuration = getStageDuration(stage) - getTaskStartTime(taskNode); + return new SimpleStatus(stageStatus.getType(), getStageDuration(stage), taskDuration); } } } + private static long getStageDuration(Stage stage) { + return stage.startTimeMillis.getMillis() + stage.durationMillis; + } + private static long getTaskStartTime(FlowNode taskNode) { return taskNode.getAction(TimingAction.class).getStartTime(); } + private static Long getTaskFinishedTime(FlowNode taskNode, TaskAction taskAction) { + Long finishedTime = taskAction.getFinishedTime(); + if (finishedTime == null && isAnyParentNodeContainingTaskFinishedAction(taskNode)) { + Optional parentNode = getParentNodeWithTaskFinishedAction(taskNode); + if (parentNode.isPresent()) { + finishedTime = parentNode.get().getAction(TaskFinishedAction.class).getFinishedTime(); + } + } + return finishedTime; + } + private static Status runningStatus(WorkflowRun build, Stage stage) throws PipelineException { int progress = progressOfStage(build, stage); return runningStatus(build.getTimeInMillis(), progress); diff --git a/src/main/java/se/diabol/jenkins/workflow/step/TaskFinishedAction.java b/src/main/java/se/diabol/jenkins/workflow/step/TaskFinishedAction.java new file mode 100644 index 000000000..dd01e7c67 --- /dev/null +++ b/src/main/java/se/diabol/jenkins/workflow/step/TaskFinishedAction.java @@ -0,0 +1,36 @@ +/* +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 . +*/ +package se.diabol.jenkins.workflow.step; + +import hudson.model.InvisibleAction; +import org.jenkinsci.plugins.workflow.actions.PersistentAction; + +import java.io.Serializable; + +public class TaskFinishedAction extends InvisibleAction implements PersistentAction, Serializable { + + private final Long finishedTime; + + public TaskFinishedAction(Long finishedTime) { + this.finishedTime = finishedTime; + } + + public Long getFinishedTime() { + return finishedTime; + } +} diff --git a/src/main/java/se/diabol/jenkins/workflow/step/TaskStepExecution.java b/src/main/java/se/diabol/jenkins/workflow/step/TaskStepExecution.java index 0eba0a2a7..d9c1dd75a 100644 --- a/src/main/java/se/diabol/jenkins/workflow/step/TaskStepExecution.java +++ b/src/main/java/se/diabol/jenkins/workflow/step/TaskStepExecution.java @@ -44,7 +44,7 @@ public class TaskStepExecution extends AbstractStepExecutionImpl { @Override public boolean start() throws Exception { - TaskAction taskAction = new TaskActionImpl(step.name); + TaskAction taskAction = new TaskActionImpl(node, step.name); StepContext context = this.getContext(); if (context.hasBody()) { ((CpsBodyInvoker) context.newBodyInvoker()) @@ -74,14 +74,17 @@ public void onResume() { } private static final class TaskActionImpl extends InvisibleAction implements TaskAction, Serializable { + + private final transient FlowNode associatedNode; private final String taskName; private Long finishedTime = null; - TaskActionImpl(String taskName) { - this(taskName, null); + TaskActionImpl(FlowNode associatedNode, String taskName) { + this(associatedNode, taskName, null); } - TaskActionImpl(String taskName, Long finishedTime) { + TaskActionImpl(FlowNode associatedNode, String taskName, Long finishedTime) { + this.associatedNode = associatedNode; this.taskName = taskName; this.finishedTime = finishedTime; } @@ -99,6 +102,7 @@ public Long getFinishedTime() { @Override public void setFinishedTime(Long finishedTime) { this.finishedTime = finishedTime; + this.associatedNode.addAction(new TaskFinishedAction(finishedTime)); } } diff --git a/src/main/java/se/diabol/jenkins/workflow/util/Util.java b/src/main/java/se/diabol/jenkins/workflow/util/Util.java index d1d0ea7c0..fc748e774 100644 --- a/src/main/java/se/diabol/jenkins/workflow/util/Util.java +++ b/src/main/java/se/diabol/jenkins/workflow/util/Util.java @@ -20,9 +20,11 @@ import org.jenkinsci.plugins.workflow.graph.FlowNode; import se.diabol.jenkins.workflow.api.Run; import se.diabol.jenkins.workflow.step.TaskAction; +import se.diabol.jenkins.workflow.step.TaskFinishedAction; import java.util.ArrayList; import java.util.List; +import java.util.Optional; public final class Util { @@ -39,6 +41,20 @@ public static List getTaskNodes(List stageNodes) { return result; } + public static Optional getParentNodeWithTaskFinishedAction(FlowNode taskNode) { + return taskNode.getParents().stream() + .filter(parent -> parent.getAction(TaskFinishedAction.class) != null) + .findFirst(); + } + + public static boolean isAnyParentNodeContainingTaskFinishedAction(FlowNode taskNode) { + if (taskNode != null && !taskNode.getParents().isEmpty()) { + Optional parentWithTaskFinishedAction = getParentNodeWithTaskFinishedAction(taskNode); + return parentWithTaskFinishedAction.isPresent(); + } + return false; + } + public static Run getRunById(List runs, int buildNumber) { if (runs == null || runs.isEmpty()) { return null; diff --git a/src/test/java/se/diabol/jenkins/workflow/util/UtilTest.java b/src/test/java/se/diabol/jenkins/workflow/util/UtilTest.java index 22374f341..f01bf2147 100644 --- a/src/test/java/se/diabol/jenkins/workflow/util/UtilTest.java +++ b/src/test/java/se/diabol/jenkins/workflow/util/UtilTest.java @@ -17,22 +17,75 @@ */ package se.diabol.jenkins.workflow.util; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertThat; - -import org.joda.time.DateTime; +import org.jenkinsci.plugins.workflow.graph.FlowNode; import org.junit.Test; 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.step.TaskFinishedAction; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Optional; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class UtilTest { + @Test + public void shouldFindParentNodeContainingTaskFinishedAction() { + FlowNode flowNode = mock(FlowNode.class); + FlowNode parent = mock(FlowNode.class); + when(parent.getAction(TaskFinishedAction.class)).thenReturn(new TaskFinishedAction(0L)); + when(flowNode.getParents()).thenReturn(Collections.singletonList(parent)); + + assertThat(Util.isAnyParentNodeContainingTaskFinishedAction(flowNode), is(true)); + } + + @Test + public void shouldNotFindParentNodeContainingTaskFinishedAction() { + FlowNode flowNode = mock(FlowNode.class); + FlowNode parent = mock(FlowNode.class); + when(parent.getAction(TaskFinishedAction.class)).thenReturn(null); + when(flowNode.getParents()).thenReturn(Collections.singletonList(parent)); + + assertThat(Util.isAnyParentNodeContainingTaskFinishedAction(flowNode), is(false)); + } + + @Test + public void shouldNotFindTaskFinishedActionForMissingParent() { + FlowNode flowNode = mock(FlowNode.class); + when(flowNode.getParents()).thenReturn(Collections.emptyList()); + + assertThat(Util.isAnyParentNodeContainingTaskFinishedAction(flowNode), is(false)); + } + + @Test + public void shouldGetParentNodeContainingTaskFinishedAction() { + FlowNode flowNode = mock(FlowNode.class); + FlowNode parent = mock(FlowNode.class); + when(parent.getAction(TaskFinishedAction.class)).thenReturn(new TaskFinishedAction(0L)); + when(flowNode.getParents()).thenReturn(Collections.singletonList(parent)); + + Optional parentNode = Util.getParentNodeWithTaskFinishedAction(flowNode); + assertThat(parentNode.isPresent(), is(true)); + } + + @Test + public void shouldNotGetParentNodeContainingTaskFinishedAction() { + FlowNode flowNode = mock(FlowNode.class); + FlowNode parent = mock(FlowNode.class); + when(parent.getAction(TaskFinishedAction.class)).thenReturn(null); + when(flowNode.getParents()).thenReturn(Collections.singletonList(parent)); + + Optional parentNode = Util.getParentNodeWithTaskFinishedAction(flowNode); + assertThat(parentNode.isPresent(), is(false)); + } + @Test public void shouldGetRunById() { Run run1 = createRun("1"); @@ -71,16 +124,12 @@ public void shouldReturnNullHeadForEmptyList() { assertThat(Util.head(Collections.emptyList()), nullValue()); } - private static List stageFixture(Long duration) { - List stages = new ArrayList<>(5); - for (int i = 1; i <= 5; i = i + 1) { - stages.add(new Stage( - "2014-04-27_20-40-00", - "Stage" + i, - "SUCCESS", - new DateTime(System.currentTimeMillis()), - duration)); - } - return stages; + @Test + public void getTaskNodesShouldFindNodesWithTaskAction() { + FlowNode node = mock(FlowNode.class); + assertThat(Util.getTaskNodes(Collections.singletonList(node)).size(), is(0)); + + when(node.getAction(TaskAction.class)).thenReturn(mock(TaskAction.class)); + assertThat(Util.getTaskNodes(Collections.singletonList(node)).size(), is(1)); } }