Skip to content

Commit

Permalink
1401 enhance process config with incident notification addresses (#1545)
Browse files Browse the repository at this point in the history
* 1401 enhance process config with incident notification adresses

* 1401 mock super class

* #1401 unittest incident notification adresses

* 1401 JavaDoc

* 1401 documentation

* repair broken footnotes

* Update digiwf-engine/digiwf-engine-service/src/main/java/de/muenchen/oss/digiwf/engine/incidents/IncidentNotifierHandler.java

Co-authored-by: Simon Hirtreiter <[email protected]>

* 1401 refactor incident noditifaction properties

* 1401 refactor properties

---------

Co-authored-by: Simon Hirtreiter <[email protected]>
  • Loading branch information
markostreich and simonhir authored Apr 12, 2024
1 parent 7238b26 commit b98047a
Show file tree
Hide file tree
Showing 12 changed files with 334 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package de.muenchen.oss.digiwf.engine.incidents;

import org.camunda.bpm.engine.impl.incident.DefaultIncidentHandler;

public class BaseIncidentHandler extends DefaultIncidentHandler {

public BaseIncidentHandler() {
super("failedJob");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package de.muenchen.oss.digiwf.engine.incidents;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "digiwf.incident")
public class IncidentNotificationProperties {

private String cockpitUrl;

private String environment;

private String fromAddress;

private String toAddress;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,92 +6,87 @@

import de.muenchen.oss.digiwf.email.api.DigiwfEmailApi;
import de.muenchen.oss.digiwf.email.model.Mail;
import de.muenchen.oss.digiwf.process.config.domain.model.ProcessConfig;
import de.muenchen.oss.digiwf.process.config.domain.service.ProcessConfigService;
import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.logging.log4j.util.Strings;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.impl.incident.DefaultIncidentHandler;
import org.camunda.bpm.engine.RuntimeService;
import org.camunda.bpm.engine.impl.incident.IncidentContext;
import org.camunda.bpm.engine.impl.persistence.entity.IncidentEntity;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.runtime.Incident;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.jetbrains.annotations.NotNull;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.util.Map;


/**
* Handler getting active in case of incidents and can react e.g. by sending an information mail.
*
* @author externer.dl.horn
*/
@Slf4j
@Component
public class IncidentNotifierHandler extends DefaultIncidentHandler {

@Autowired
private DigiwfEmailApi digiwfEmailApi;
@RequiredArgsConstructor(onConstructor_ = { @Lazy })
@EnableConfigurationProperties(IncidentNotificationProperties.class)
public class IncidentNotifierHandler extends BaseIncidentHandler {

@Autowired
@Lazy
private RepositoryService repositoryService;
private final RepositoryService repositoryService;

@Value("${digiwf.incident.cockpitUrl:#{null}}")
private String cockpitUrl;
private final RuntimeService runtimeService;

@Value("${digiwf.incident.fromAddress:#{null}}")
private String fromAddress;
private final DigiwfEmailApi digiwfEmailApi;

@Value("${digiwf.incident.toAddress:#{null}}")
private String toAddress;
private final ProcessConfigService processConfigService;

@Value("${digiwf.incident.environment:#{null}}")
private String environment;

public IncidentNotifierHandler() {
super("failedJob");
}
private final IncidentNotificationProperties incidentNotificationProperties;

@Override
public Incident handleIncident(final IncidentContext context, final String message) {
log.warn("Incident occurred");
final IncidentEntity incidentEntity = (IncidentEntity) super.handleIncident(context, message);
final IncidentEntity incidentEntity = superHandleIncident(context, message);

val processInstanceId = incidentEntity.getProcessInstanceId();
val rootProcessInstanceId = runtimeService
.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult()
.getRootProcessInstanceId();

val rootProcessDefinitionId = runtimeService
.createProcessInstanceQuery()
.processInstanceId(rootProcessInstanceId)
.singleResult()
.getProcessDefinitionId();
val processConfig = processConfigService.getProcessConfig(rootProcessDefinitionId.split(":")[0]);

var notificationAddresses = processConfig.orElse(new ProcessConfig()).getIncidentNotificationAddresses();

if (Strings.isEmpty(this.toAddress)) {
log.debug("Notification on incidents if not configured");
if (Strings.isEmpty(notificationAddresses)) notificationAddresses = incidentNotificationProperties.getToAddress();

if (Strings.isEmpty(notificationAddresses)) {
log.debug("Notification on incidents is not configured");
return incidentEntity;
}

try {
String processName = this.getProcessName(incidentEntity.getProcessDefinitionId());
val link = this.cockpitUrl +
"camunda/app/cockpit/default/#/process-instance/" +
incidentEntity.getProcessInstanceId() +
"/runtime";
final String emailText = processName.isBlank() ?
"In der Anwendung ist ein Incident aufgetreten." :
"In der Anwendung ist ein Incident aufgetreten (Prozessname: " + processName + ").";

final Map<String, String> emailContent = Map.of(
"%%body_top%%", emailText,
"%%body_bottom%%", "Mit freundlichen Grüßen<br>Ihr DigiWF-Team",
"%%button_link%%", link,
"%%button_text%%", "Fehler im Cockpit anzeigen",
"%%footer%%", "DigiWF 2.0<br>IT-Referat der Stadt München"
);
final Map<String, String> emailContent = getEMailContent(incidentEntity, processName);
final String templatePath = "bausteine/mail/templatewithlink/mail-template.tpl";
final String emailBody = this.digiwfEmailApi.getEmailBodyFromTemplate(templatePath, emailContent);

final Mail mail = Mail.builder()
.receivers(this.toAddress)
.subject(this.environment + ": Incident aufgetreten")
.receivers(notificationAddresses)
.subject(incidentNotificationProperties.getEnvironment() + ": Incident aufgetreten")
.body(emailBody)
.htmlBody(true)
.replyTo(this.fromAddress)
.replyTo(incidentNotificationProperties.getFromAddress())
.build();
this.digiwfEmailApi.sendMailWithDefaultLogo(mail);
} catch (final MessagingException error) {
Expand All @@ -101,22 +96,51 @@ public Incident handleIncident(final IncidentContext context, final String messa
return incidentEntity;
}

private String getProcessName(String processDefinitionId){
/**
* Retrieves the email content for the incident notification email. This includes the process name, a link to the incident in the Camunda Cockpit, and a
* predefined email template.
*
* @param incidentEntity The IncidentEntity representing the incident.
* @param processName The name of the process associated with the incident.
* @return A Map containing the email content key-value pairs.
*/
@NotNull
private Map<String, String> getEMailContent(final IncidentEntity incidentEntity, final String processName) {
val link = incidentNotificationProperties.getCockpitUrl() +
"camunda/app/cockpit/default/#/process-instance/" +
incidentEntity.getProcessInstanceId() +
"/runtime";
final String emailText = processName.isBlank() ?
"In der Anwendung ist ein Incident aufgetreten." :
"In der Anwendung ist ein Incident aufgetreten (Prozessname: " + processName + ").";

return Map.of(
"%%body_top%%", emailText,
"%%body_bottom%%", "Mit freundlichen Grüßen<br>Ihr DigiWF-Team",
"%%button_link%%", link,
"%%button_text%%", "Fehler im Cockpit anzeigen",
"%%footer%%", "DigiWF 2.0<br>IT-Referat der Stadt München"
);
}

private String getProcessName(String processDefinitionId) {
String processName = "";
try {
ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefinitionId).singleResult();
if(procDef.getName() != null && !procDef.getName().isBlank()) {
if (procDef.getName() != null && !procDef.getName().isBlank()) {
processName = procDef.getName();
}
else {
if(procDef.getKey() != null && !procDef.getKey().isBlank()){
} else {
if (procDef.getKey() != null && !procDef.getKey().isBlank()) {
processName = procDef.getKey();
}
}
}
catch (Exception ex){
} catch (Exception ex) {
log.warn("Reading ProcessDefinition failed: {}", ex.getMessage());
}
return processName;
}

IncidentEntity superHandleIncident(final IncidentContext context, final String message) {
return (IncidentEntity) super.handleIncident(context, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,19 @@
@NoArgsConstructor
public class ProcessConfig {


public static final String INSTANCE_FILE_PATHS_READONLY = "app_instance_file_paths_readonly";
public static final String INSTANCE_FILE_PATHS = "app_instance_file_paths";
public static final String INSTANCE_SCHEMA_KEY = "app_instance_schema_key";

/**
* This constant holds the configuration key for email addresses that should be notified in case of an incident.
* These addresses are used to send out alerts or notifications when an incident occurs within the application.
* <p>
* The expected format for the email addresses is a comma-separated list of valid email addresses.
* Example: "[email protected],[email protected]"
*/
public static final String INCIDENT_NOTIFICATION_ADDRESSES = "app_incident_notification_addresses";

/**
* key of the process config.
*/
Expand Down Expand Up @@ -91,4 +99,8 @@ public String getInstanceSchemaKey() {
return this.getConfig(INSTANCE_SCHEMA_KEY);
}

public String getIncidentNotificationAddresses() {
return this.getConfig(INCIDENT_NOTIFICATION_ADDRESSES);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,10 @@ digiwf:
autodeployBausteine: true
whitelist: '^[\w\s\?\!\#\: \.§\%\/\(\);:,@\u20AC\-\''\u00C0-\u017F]*$'
incident:
cockpiturl: ${COCKPIT_URL}
cockpit-url: ${COCKPIT_URL}
environment: ${DIGIWF_ENV}
fromaddress: itm.digiwf@muenchen.de
toaddress: [email protected]
from-address: noreply@muenchen.de
to-address: [email protected]
jsonschema:
autodeploy: true
mail:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"key": "TestProcessIncident",
"statusConfig": [
{
"key": "Offen",
"label": "Offen",
"position": 1
},
{
"key": "inBear",
"label": "In Bearbeitung",
"position": 2
},
{
"key": "abgeschlossen",
"label": "Abgeschlossen",
"position": 3
}
],
"configs": [
{
"key": "app_file_paths_readonly",
"value": "Documents/test4;Documents/test5;Documents/test6"
},
{
"key": "app_file_paths",
"value": "Documents/test1;Documents/test2;Documents/test3"
},
{
"key": "app_instance_file_paths",
"value": "Documents"
},
{
"key": "app_instance_schema_key",
"value": "TestIncident_Start_V01"
},
{
"key": "app_incident_notification_addresses",
"value": "[email protected]"
}
]
}
Loading

0 comments on commit b98047a

Please sign in to comment.