first implementation of wf history

This commit is contained in:
Michele Artini 2022-06-30 14:43:26 +02:00
parent 2b10c4582a
commit daa8b1a14e
9 changed files with 426 additions and 0 deletions

View File

@ -0,0 +1,14 @@
package eu.dnetlib.is.wfs;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class WfHistoryController {
@GetMapping("/wf_history")
public void wfHistory(final ModelMap map) {
}
}

View File

@ -0,0 +1,81 @@
package eu.dnetlib.is.wfs;
import java.io.File;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.dnetlib.is.wf.model.WfProcessExecution;
import eu.dnetlib.is.wf.repository.WfProcessExecutionRepository;
@Component
public class WfHistoryImporter {
private static final Log log = LogFactory.getLog(WfHistoryImporter.class);
@Autowired
private WfProcessExecutionRepository wfProcessExecutionRepository;
public void load(final String path) throws Exception {
final ObjectMapper mapper = new ObjectMapper();
final JsonNode rootNode = mapper.readTree(new File(path));
rootNode.forEach(wf -> saveWf(wf));
}
private void saveWf(final JsonNode node) {
final WfProcessExecution wf = new WfProcessExecution();
wf.setProcessId(node.get("system:processId").asText());
wf.setName(node.get("system:wfName").asText());
wf.setFamily(node.get("system:profileFamily").asText());
if (node.has("dataprovider:id")) {
wf.setDsId(node.get("dataprovider:id").asText());
}
if (node.has("dataprovider:name")) {
wf.setDsName(node.get("dataprovider:name").asText());
}
if (node.has("dataprovider:interface")) {
wf.setDsApi(node.get("dataprovider:interface").asText());
}
wf.setStartDate(new Date(NumberUtils.toLong(node.get("system:startDate").asText())));
wf.setEndDate(new Date(NumberUtils.toLong(node.get("system:endDate").asText())));
if (BooleanUtils.toBoolean(node.get("system:isCompletedSuccessfully").asText())) {
wf.setStatus("success");
} else {
wf.setStatus("failure");
}
final Map<String, String> details = new LinkedHashMap<>();
final Iterator<Entry<String, JsonNode>> fields = node.fields();
while (fields.hasNext()) {
final Entry<String, JsonNode> f = fields.next();
if (f.getValue().isValueNode()) {
details.put(f.getKey(), f.getValue().asText());
}
}
wf.setDetails(details);
wfProcessExecutionRepository.save(wf);
log.info("Wf saved with id: " + wf.getProcessId());
}
}

View File

@ -0,0 +1,56 @@
package eu.dnetlib.is.wfs;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import eu.dnetlib.is.wf.model.WfProcessExecution;
import eu.dnetlib.is.wf.repository.WfProcessExecutionRepository;
@RestController
@RequestMapping("/api/wfs")
public class WfHistoryRestController {
@Autowired
private WfProcessExecutionRepository wfProcessExecutionRepository;
@Autowired
private WfHistoryImporter wfHistoryImporter;
@GetMapping("/")
public List<WfProcessExecution> history(@RequestParam(required = false) final Date from, @RequestParam(required = false) final Date to) {
if (from == null && to == null) {
return wfProcessExecutionRepository.findAll(PageRequest.of(0, 100, Sort.by("endDate").descending())).toList();
} else if (from == null) {
return wfProcessExecutionRepository.findByEndDateBetweenOrderByEndDateDesc(new Date(0), to);
} else if (to == null) {
return wfProcessExecutionRepository.findByEndDateBetweenOrderByEndDateDesc(from, new Date());
} else {
return wfProcessExecutionRepository.findByEndDateBetweenOrderByEndDateDesc(from, to);
}
}
@GetMapping("/{processId}")
public WfProcessExecution getProcess(@PathVariable final String processId) {
return wfProcessExecutionRepository.findById(processId).get();
}
@GetMapping(value = "/load")
public List<String> loadFromOldProfile(@RequestParam final String path) throws Exception {
// mongoexport -d dnet_logs -c wf_logs --jsonArray -o /tmp/mongodump.json
wfHistoryImporter.load(path);
return Arrays.asList("Done.");
}
}

View File

@ -0,0 +1,22 @@
var app = angular.module('wfHistoryApp', []);
app.controller('wfHistoryController', function($scope, $http) {
$scope.workflows = [];
$scope.currentWf = {};
$scope.reload = function() {
$http.get('./api/wfs/?' + $.now()).then(function successCallback(res) {
$scope.workflows = res.data;
}, function errorCallback(res) {
alert('ERROR: ' + res.data.message);
});
};
$scope.setCurrentWf = function(wf) {
$scope.currentWf = angular.copy(wf);
}
$scope.reload();
});

View File

@ -36,6 +36,12 @@
<a class="dropdown-item" href="./transformationRules">Transformation rules</a>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="javascript:void(0)" data-toggle="dropdown">Logs</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="./wf_history">Workflow history</a>
</div>
</li>
</ul>
</div>
</nav>

View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head th:replace="fragments/mainParts.html :: htmlHeader('Workflow history')"></head>
<body ng-app="wfHistoryApp" ng-controller="wfHistoryController">
<nav th:replace="fragments/mainParts.html :: mainMenu('Workflow history')"></nav>
<div class="container-fluid">
<div class="row">
<div class="col">
<p ng-show="workflows.length > 0">
<input type="text" class="form-control form-control-sm" ng-model="wfFilter" placeholder="Filter..."/>
</p>
<p>
<span class="text-muted"><b>Total workflows :</b> {{(workflows | filter:wfFilter).length}}</span>
</p>
<table class="table table-sm table-striped small">
<thead>
<tr>
<th style="width: 10%">Process Id</th>
<th style="width: 20%">Workflow Name</th>
<th style="width: 10%">Workflow Family</th>
<th style="width: 30%">Datasource</th>
<th style="width: 10%">Status</th>
<th style="width: 10%">Start Date</th>
<th style="width: 10%">End Date</th>
</tr>
</thead>
<tbody>
<tr ng-show="(workflows|filter:wfFilter).length == 0">
<td colspan="7" class="text-muted">no workflows</td>
</tr>
<tr ng-repeat="wf in workflows|filter:wfFilter">
<th><a href="javascript:void(0)" data-toggle="modal" data-target="#showWfDetailsModal" ng-click="setCurrentWf(wf)">{{wf.processId}}</a></th>
<td>{{wf.name}}</td>
<td>{{wf.family}}</td>
<td>{{wf.dsName}}</td>
<td>{{wf.status}}</td>
<td>{{wf.startDate}}</td>
<td>{{wf.endDate}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Modals -->
<div class="modal fade" tabindex="-1" id="showWfDetailsModal">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" ng-if="mode == 'new'">Details</h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
{{currentWf.details}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</body>
<th:block th:replace="fragments/mainParts.html :: scripts"></th:block>
<script src="js/wf_history.js"></script>
</html>

View File

@ -0,0 +1,145 @@
package eu.dnetlib.is.wf.model;
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
import com.vladmihalcea.hibernate.type.json.JsonStringType;
@Entity
@Table(name = "wf_history")
@TypeDefs({
@TypeDef(name = "json", typeClass = JsonStringType.class),
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class WfProcessExecution implements Serializable {
private static final long serialVersionUID = -326994850248506828L;
@Id
@Column(name = "process_id")
private String processId;
@Column(name = "name")
private String name;
@Column(name = "family")
private String family;
@Column(name = "status")
private String status;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "start_date")
private Date startDate;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "end_date")
private Date endDate;
@Column(name = "ds_id")
private String dsId;
@Column(name = "ds_name")
private String dsName;
@Column(name = "ds_api")
private String dsApi;
@Type(type = "jsonb")
@Column(name = "details")
private Map<String, String> details;
public String getProcessId() {
return processId;
}
public void setProcessId(final String processId) {
this.processId = processId;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getFamily() {
return family;
}
public void setFamily(final String family) {
this.family = family;
}
public String getStatus() {
return status;
}
public void setStatus(final String status) {
this.status = status;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(final Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(final Date endDate) {
this.endDate = endDate;
}
public String getDsId() {
return dsId;
}
public void setDsId(final String dsId) {
this.dsId = dsId;
}
public String getDsName() {
return dsName;
}
public void setDsName(final String dsName) {
this.dsName = dsName;
}
public String getDsApi() {
return dsApi;
}
public void setDsApi(final String dsApi) {
this.dsApi = dsApi;
}
public Map<String, String> getDetails() {
return details;
}
public void setDetails(final Map<String, String> details) {
this.details = details;
}
}

View File

@ -0,0 +1,13 @@
package eu.dnetlib.is.wf.repository;
import java.util.Date;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import eu.dnetlib.is.wf.model.WfProcessExecution;
public interface WfProcessExecutionRepository extends JpaRepository<WfProcessExecution, String> {
List<WfProcessExecution> findByEndDateBetweenOrderByEndDateDesc(Date start, Date end);
}

View File

@ -63,3 +63,17 @@ CREATE INDEX ON context_cat_concepts_lvl_0 (parent);
CREATE INDEX ON context_cat_concepts_lvl_1 (parent);
CREATE INDEX ON context_cat_concepts_lvl_2 (parent);
-- WF History
CREATE TABLE wf_history (
process_id text PRIMARY KEY,
name text NOT NULL,
family text NOT NULL,
status text NOT NULL,
start_date timestamp NOT NULL,
end_date timestamp NOT NULL,
ds_id text,
ds_name text,
ds_api text,
details jsonb
);