AGL Metrics eingeführt (draft)
This commit is contained in:
parent
03d4fe241a
commit
d5d4b21434
4 changed files with 4668 additions and 0 deletions
|
|
@ -40,6 +40,8 @@ services:
|
||||||
LLM_SERVICE_ENDPOINT_USER: "admin"
|
LLM_SERVICE_ENDPOINT_USER: "admin"
|
||||||
LLM_SERVICE_ENDPOINT_PASSWORD: "imixs4.null"
|
LLM_SERVICE_ENDPOINT_PASSWORD: "imixs4.null"
|
||||||
|
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
- "9990:9990"
|
- "9990:9990"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
package com.alexanderlogistics;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.imixs.marty.team.TeamService;
|
||||||
|
import org.imixs.workflow.ItemCollection;
|
||||||
|
import org.imixs.workflow.ItemCollectionComparator;
|
||||||
|
import org.imixs.workflow.engine.DocumentService;
|
||||||
|
import org.imixs.workflow.exceptions.PluginException;
|
||||||
|
import org.imixs.workflow.exceptions.QueryException;
|
||||||
|
|
||||||
|
import jakarta.ejb.Stateless;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.ws.rs.GET;
|
||||||
|
import jakarta.ws.rs.Path;
|
||||||
|
import jakarta.ws.rs.Produces;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
|
@Path("metrics")
|
||||||
|
@Stateless
|
||||||
|
public class MetricDebitorRestService {
|
||||||
|
|
||||||
|
private static Logger logger = Logger.getLogger(MetricDebitorRestService.class.getName());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DocumentService documentService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MetricDebitorService metricService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TeamService teamService;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/ping")
|
||||||
|
@Produces({ MediaType.TEXT_PLAIN })
|
||||||
|
public String ping() {
|
||||||
|
logger.info("GET ping");
|
||||||
|
return "ping: " + System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method refreshes all debitor metrics by iterating through the metric
|
||||||
|
* entities.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/refresh")
|
||||||
|
@Produces({ MediaType.TEXT_PLAIN })
|
||||||
|
public Response refreshMetrics() {
|
||||||
|
try {
|
||||||
|
// Alle Debitoren Metriken laden
|
||||||
|
String query = "(type:" + MetricDebitorService.TYPE_METRIC_DEBITOR + ")";
|
||||||
|
List<ItemCollection> metrics = documentService.find(query, 9999, 0);
|
||||||
|
|
||||||
|
// Metriken initialisieren
|
||||||
|
for (ItemCollection metric : metrics) {
|
||||||
|
metricService.initMetric(metric);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.ok().entity("Initialized " + metrics.size() + " debitor metrics").build();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Response.serverError()
|
||||||
|
.entity("Failed to initialize metrics: " + e.getMessage())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method initializes the metrics for all debitors with open invoices.
|
||||||
|
* The method creates or updates the metric entires for each debitor.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GET
|
||||||
|
@Path("/init")
|
||||||
|
@Produces({ MediaType.TEXT_PLAIN })
|
||||||
|
public Response initMetrics() {
|
||||||
|
Map<String, DbtrData> dbtrDataCache = new HashMap<String, DbtrData>();
|
||||||
|
long l = System.currentTimeMillis();
|
||||||
|
logger.info("├── init metrics...");
|
||||||
|
try {
|
||||||
|
// compute all departments
|
||||||
|
List<String> spaceNames = new ArrayList<String>();
|
||||||
|
List<ItemCollection> spaces = teamService.getSpaces();
|
||||||
|
// sort by space.name
|
||||||
|
Collections.sort(spaces, new ItemCollectionComparator("space.name", true));
|
||||||
|
for (ItemCollection space : spaces) {
|
||||||
|
String spaceName = space.getItemValueString("space.name");
|
||||||
|
spaceNames.add(spaceName);
|
||||||
|
logger.info("│ ├── grouping invoices by debitor...");
|
||||||
|
groupInvoicesByWeek(space.getUniqueID(), spaceName, dbtrDataCache);
|
||||||
|
}
|
||||||
|
logger.info("│ ├── grouping invoices finished in " + (System.currentTimeMillis() - l) + "ms");
|
||||||
|
|
||||||
|
rebuildMetrics(dbtrDataCache);
|
||||||
|
logger.info("├── init metrics completed in " + (System.currentTimeMillis() - l) + "ms");
|
||||||
|
return Response.ok().entity("init metrics completed in " + (System.currentTimeMillis() - l) + "ms").build();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Response.serverError()
|
||||||
|
.entity("Failed to initialize metrics: " + e.getMessage())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuildMetrics(Map<String, DbtrData> dbtrDataCache) throws PluginException {
|
||||||
|
logger.info("│ ├── rebuild metrics...");
|
||||||
|
|
||||||
|
for (String dbtrNumber : dbtrDataCache.keySet()) {
|
||||||
|
DbtrData dbtrData = dbtrDataCache.get(dbtrNumber);
|
||||||
|
String dbtrName = dbtrData.dbtrName;
|
||||||
|
|
||||||
|
ItemCollection metric = metricService.loadMetric(dbtrNumber, dbtrName);
|
||||||
|
metric.setItemValue(MetricDebitorService.ITEM_SALDO, dbtrData.getTotal("EUR"));
|
||||||
|
documentService.save(metric);
|
||||||
|
metricService.initMetric(metric);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diese Methode gruppiert eine Rechnungsliste nach debitoren
|
||||||
|
*
|
||||||
|
* @param spaceID - Space Ref to select a list of invoices associated with
|
||||||
|
* a
|
||||||
|
* space
|
||||||
|
* @param dbtrDataCache - a local cache storing all invoices by week
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private void groupInvoicesByWeek(String spaceID, String spaceName,
|
||||||
|
Map<String, DbtrData> dbtrDataCache) {
|
||||||
|
|
||||||
|
logger.info("│ │ ├── group invoices for " + spaceName + "/" + spaceID);
|
||||||
|
try {
|
||||||
|
List<ItemCollection> invoices = documentService.find(
|
||||||
|
"$modelversion:rechnungsausgang-* AND type:workitem AND $uniqueidref:" + spaceID, 9999, 0,
|
||||||
|
"invoice.number", false);
|
||||||
|
|
||||||
|
logger.fine(" found " + invoices.size() + " for space " + spaceName);
|
||||||
|
for (ItemCollection invoice : invoices) {
|
||||||
|
|
||||||
|
String debitorName = invoice.getItemValueString("dbtr.name");
|
||||||
|
String debitorNumber = invoice.getItemValueString("dbtr.number");
|
||||||
|
|
||||||
|
DbtrData invoiceData = dbtrDataCache.get(debitorNumber);
|
||||||
|
if (invoiceData == null) {
|
||||||
|
invoiceData = new DbtrData(debitorNumber, debitorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jetzt Rechnung addieren
|
||||||
|
invoiceData.add(invoice);
|
||||||
|
dbtrDataCache.put(debitorNumber, invoiceData);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (QueryException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Element for invoice totals by debitor.
|
||||||
|
* The object holds multiple currencies.
|
||||||
|
*/
|
||||||
|
class DbtrData {
|
||||||
|
|
||||||
|
String dbtrName;
|
||||||
|
String dbtrNumber;
|
||||||
|
|
||||||
|
Map<String, Double> totals = new HashMap<>();
|
||||||
|
|
||||||
|
public DbtrData(String dbtrNumber, String dbtrName) {
|
||||||
|
this.dbtrName = dbtrName;
|
||||||
|
this.dbtrNumber = dbtrNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(ItemCollection invoice) {
|
||||||
|
String currency = invoice.getItemValueString("invoice.currency");
|
||||||
|
Double total = invoice.getItemValueDouble("invoice.saldo");
|
||||||
|
Double totalCurrency = totals.get(currency);
|
||||||
|
if (totalCurrency == null) {
|
||||||
|
totalCurrency = 0.0;
|
||||||
|
}
|
||||||
|
totalCurrency = totalCurrency + total;
|
||||||
|
totals.put(currency, InvoiceUtil.round(totalCurrency));
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotal(String currency) {
|
||||||
|
Double result = totals.get(currency);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
package com.alexanderlogistics;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.eclipse.microprofile.metrics.Metadata;
|
||||||
|
import org.eclipse.microprofile.metrics.MetricRegistry;
|
||||||
|
import org.eclipse.microprofile.metrics.Tag;
|
||||||
|
import org.eclipse.microprofile.metrics.annotation.RegistryScope;
|
||||||
|
import org.imixs.workflow.ItemCollection;
|
||||||
|
import org.imixs.workflow.engine.DocumentService;
|
||||||
|
import org.imixs.workflow.engine.ProcessingEvent;
|
||||||
|
import org.imixs.workflow.exceptions.PluginException;
|
||||||
|
import org.imixs.workflow.exceptions.QueryException;
|
||||||
|
|
||||||
|
import jakarta.annotation.security.DeclareRoles;
|
||||||
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
|
import jakarta.annotation.security.RunAs;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
import jakarta.enterprise.event.Observes;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dieser Service reagiert auch ProcessingEvents und speichert/aktualisert die
|
||||||
|
* zugehörige Debitoren Metric Entity.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
|
||||||
|
"org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
|
||||||
|
"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
|
||||||
|
@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
|
||||||
|
"org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
|
||||||
|
"org.imixs.ACCESSLEVEL.MANAGERACCESS" })
|
||||||
|
@RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS")
|
||||||
|
@ApplicationScoped
|
||||||
|
public class MetricDebitorService {
|
||||||
|
|
||||||
|
private static Logger logger = Logger.getLogger(MetricDebitorService.class.getName());
|
||||||
|
private final ConcurrentHashMap<String, Double> currentSaldos = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static final String METRIC_INVOICES = "invoices";
|
||||||
|
public static final String TYPE_METRIC_DEBITOR = "metric.debitor";
|
||||||
|
public static final String ITEM_LAST_METRIC = "metric.saldo";
|
||||||
|
public static final String ITEM_SALDO = "invoice.saldo";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@RegistryScope(scope = MetricRegistry.APPLICATION_SCOPE)
|
||||||
|
MetricRegistry metricRegistry;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DocumentService documentService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process Metric only if some data has changed....
|
||||||
|
*
|
||||||
|
* @param processingEvent
|
||||||
|
* @throws PluginException
|
||||||
|
*/
|
||||||
|
public void onProcessingEvent(@Observes ProcessingEvent processingEvent) throws PluginException {
|
||||||
|
double debitorSaldo = 0;
|
||||||
|
ItemCollection invoice = processingEvent.getDocument();
|
||||||
|
if (!invoice.getModelVersion().startsWith("rechnungsausgang-")) {
|
||||||
|
// skip event
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify if saldo has changed.....
|
||||||
|
double invoiceSaldo = invoice.getItemValueDouble(ITEM_SALDO);
|
||||||
|
if (invoiceSaldo == invoice.getItemValueDouble(ITEM_LAST_METRIC)) {
|
||||||
|
// no change - no metric update!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update metric and cache last value
|
||||||
|
if (processingEvent.getEventType() == ProcessingEvent.AFTER_PROCESS) {
|
||||||
|
String dbtrNumber = invoice.getItemValueString("dbtr.number");
|
||||||
|
String dbtrName = invoice.getItemValueString("dbtr.name");
|
||||||
|
|
||||||
|
ItemCollection metric = loadMetric(dbtrNumber, dbtrName);
|
||||||
|
|
||||||
|
// Saldo-Berechnung wie bisher
|
||||||
|
debitorSaldo = metric.getItemValueDouble(ITEM_SALDO);
|
||||||
|
if (invoice.hasItem(ITEM_LAST_METRIC)) {
|
||||||
|
debitorSaldo = debitorSaldo - invoice.getItemValueDouble(ITEM_LAST_METRIC);
|
||||||
|
}
|
||||||
|
debitorSaldo = InvoiceUtil.round(debitorSaldo + invoiceSaldo);
|
||||||
|
|
||||||
|
// Speichern in der Datenbank
|
||||||
|
metric.setItemValue(ITEM_SALDO, debitorSaldo);
|
||||||
|
logger.info("--- update Debitor Saldo " + dbtrNumber + " -> " + debitorSaldo);
|
||||||
|
documentService.save(metric);
|
||||||
|
|
||||||
|
// Aktuellen Saldo im Memory speichern
|
||||||
|
currentSaldos.put(dbtrNumber, debitorSaldo);
|
||||||
|
// Gauge registrieren
|
||||||
|
registerGauge(dbtrNumber, metric.getItemValueString("dbtr.name"));
|
||||||
|
logger.info("--- new gauge value for " + dbtrNumber + " -> " + debitorSaldo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method loads a metric entity. If no metric entity exits, the method
|
||||||
|
* creates a new one.
|
||||||
|
*
|
||||||
|
* @param dbtrID
|
||||||
|
* @return
|
||||||
|
* @throws PluginException
|
||||||
|
*/
|
||||||
|
public ItemCollection loadMetric(String dbtrNumber, String dbtrName) throws PluginException {
|
||||||
|
|
||||||
|
ItemCollection debitorMetric = null;
|
||||||
|
if (dbtrNumber == null || dbtrNumber.isEmpty()) {
|
||||||
|
throw new PluginException(PluginException.class.getName(), "QUERY ERROR",
|
||||||
|
"missing debitor number");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String query = "(type:" + TYPE_METRIC_DEBITOR + ") AND (name:" + dbtrNumber + ")";
|
||||||
|
List<ItemCollection> result = documentService.find(query, 1, 0, "$modified", true);
|
||||||
|
if (result.size() > 0) {
|
||||||
|
debitorMetric = result.get(0);
|
||||||
|
}
|
||||||
|
if (debitorMetric == null) {
|
||||||
|
// create a new instance
|
||||||
|
logger.info("creating new debitor metric");
|
||||||
|
debitorMetric = new ItemCollection();
|
||||||
|
debitorMetric.setType(TYPE_METRIC_DEBITOR);
|
||||||
|
debitorMetric.setItemValue("name", dbtrNumber);
|
||||||
|
debitorMetric.setItemValue("dbtr.number", dbtrNumber);
|
||||||
|
debitorMetric.setItemValue("dbtr.name", dbtrName);
|
||||||
|
}
|
||||||
|
} catch (QueryException e) {
|
||||||
|
throw new PluginException(PluginException.class.getName(), "QUERY ERROR", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return debitorMetric;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hilfsmethode um Metriken initial aufzubauen. Wird vom Metric Rest Service
|
||||||
|
* verwendet.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void initMetric(ItemCollection metric) {
|
||||||
|
String dbtrNumber = metric.getItemValueString("dbtr.number");
|
||||||
|
double saldo = metric.getItemValueDouble(ITEM_SALDO);
|
||||||
|
|
||||||
|
// Aktuellen Saldo in Map speichern
|
||||||
|
currentSaldos.put(dbtrNumber, saldo);
|
||||||
|
// Gauge registrieren
|
||||||
|
registerGauge(dbtrNumber, metric.getItemValueString("dbtr.name"));
|
||||||
|
logger.info("Initialized metric for debitor " + dbtrNumber + " -> " + saldo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to register a gauge for a debitor
|
||||||
|
*
|
||||||
|
* @param dbtrNumber - the debitor number
|
||||||
|
* @param dbtrName - the debitor name
|
||||||
|
*/
|
||||||
|
private void registerGauge(String dbtrNumber, String dbtrName) {
|
||||||
|
List<Tag> tags = new ArrayList<>();
|
||||||
|
tags.add(new Tag("type", "dbtr"));
|
||||||
|
tags.add(new Tag("number", dbtrNumber));
|
||||||
|
tags.add(new Tag("name", dbtrName));
|
||||||
|
|
||||||
|
Metadata metadata = Metadata.builder()
|
||||||
|
.withName("dbtr.invoice.saldo")
|
||||||
|
.withDescription("Debitor Balance")
|
||||||
|
.withUnit("EUR")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
metricRegistry.gauge(metadata,
|
||||||
|
() -> currentSaldos.getOrDefault(dbtrNumber, 0.0),
|
||||||
|
tags.toArray(new Tag[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
4280
workflow/rechnungsausgang-de-1.0.14.bpmn
Normal file
4280
workflow/rechnungsausgang-de-1.0.14.bpmn
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue