Skip to content
This repository has been archived by the owner on Sep 9, 2024. It is now read-only.

Commit

Permalink
JENKINS-54191 Persist task finished time
Browse files Browse the repository at this point in the history
  • Loading branch information
tommysdk committed Oct 31, 2018
1 parent f318332 commit 8ba2da1
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 29 deletions.
31 changes: 24 additions & 7 deletions src/main/java/se/diabol/jenkins/workflow/model/Task.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -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<FlowNode> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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;
}
Expand All @@ -99,6 +102,7 @@ public Long getFinishedTime() {
@Override
public void setFinishedTime(Long finishedTime) {
this.finishedTime = finishedTime;
this.associatedNode.addAction(new TaskFinishedAction(finishedTime));
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/main/java/se/diabol/jenkins/workflow/util/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -39,6 +41,20 @@ public static List<FlowNode> getTaskNodes(List<FlowNode> stageNodes) {
return result;
}

public static Optional<FlowNode> 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<FlowNode> parentWithTaskFinishedAction = getParentNodeWithTaskFinishedAction(taskNode);
return parentWithTaskFinishedAction.isPresent();
}
return false;
}

public static Run getRunById(List<Run> runs, int buildNumber) {
if (runs == null || runs.isEmpty()) {
return null;
Expand Down
85 changes: 67 additions & 18 deletions src/test/java/se/diabol/jenkins/workflow/util/UtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<FlowNode> 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<FlowNode> parentNode = Util.getParentNodeWithTaskFinishedAction(flowNode);
assertThat(parentNode.isPresent(), is(false));
}

@Test
public void shouldGetRunById() {
Run run1 = createRun("1");
Expand Down Expand Up @@ -71,16 +124,12 @@ public void shouldReturnNullHeadForEmptyList() {
assertThat(Util.head(Collections.emptyList()), nullValue());
}

private static List<Stage> stageFixture(Long duration) {
List<Stage> 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));
}
}

0 comments on commit 8ba2da1

Please sign in to comment.