Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.openmetadata.service.events.subscription.AlertsRuleEvaluator Maven / Gradle / Ivy
package org.openmetadata.service.events.subscription;
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
import static org.openmetadata.schema.type.Function.ParameterType.ALL_INDEX_ELASTIC_SEARCH;
import static org.openmetadata.schema.type.Function.ParameterType.READ_FROM_PARAM_CONTEXT;
import static org.openmetadata.schema.type.Function.ParameterType.READ_FROM_PARAM_CONTEXT_PER_ENTITY;
import static org.openmetadata.schema.type.Function.ParameterType.SPECIFIC_INDEX_ELASTIC_SEARCH;
import static org.openmetadata.schema.type.ThreadType.Conversation;
import static org.openmetadata.service.Entity.INGESTION_PIPELINE;
import static org.openmetadata.service.Entity.PIPELINE;
import static org.openmetadata.service.Entity.TEAM;
import static org.openmetadata.service.Entity.TEST_CASE;
import static org.openmetadata.service.Entity.THREAD;
import static org.openmetadata.service.Entity.USER;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.schema.EntityInterface;
import org.openmetadata.schema.Function;
import org.openmetadata.schema.entity.feed.Thread;
import org.openmetadata.schema.entity.services.ingestionPipelines.PipelineStatus;
import org.openmetadata.schema.entity.services.ingestionPipelines.PipelineStatusType;
import org.openmetadata.schema.entity.teams.Team;
import org.openmetadata.schema.entity.teams.User;
import org.openmetadata.schema.tests.TestCase;
import org.openmetadata.schema.tests.type.TestCaseResult;
import org.openmetadata.schema.tests.type.TestCaseStatus;
import org.openmetadata.schema.type.ChangeEvent;
import org.openmetadata.schema.type.EntityReference;
import org.openmetadata.schema.type.FieldChange;
import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.type.Post;
import org.openmetadata.schema.type.StatusType;
import org.openmetadata.service.Entity;
import org.openmetadata.service.formatter.util.FormatterUtil;
import org.openmetadata.service.resources.feeds.MessageParser;
import org.openmetadata.service.util.JsonUtils;
@Slf4j
public class AlertsRuleEvaluator {
private final ChangeEvent changeEvent;
public AlertsRuleEvaluator(ChangeEvent event) {
this.changeEvent = event;
}
@Function(
name = "matchAnySource",
input = "List of comma separated source",
description =
"Returns true if the change event entity being accessed has source as mentioned in condition",
examples = {"matchAnySource({'bot', 'user'})"},
paramInputType = READ_FROM_PARAM_CONTEXT)
public boolean matchAnySource(List originEntities) {
if (changeEvent == null || changeEvent.getEntityType() == null) {
return false;
}
// Filter does not apply to Thread Change Events
if (changeEvent.getEntityType().equals(THREAD)) {
return true;
}
String changeEventEntity = changeEvent.getEntityType();
for (String entityType : originEntities) {
if (changeEventEntity.equals(entityType)) {
return true;
}
}
return false;
}
@Function(
name = "matchAnyOwnerName",
input = "List of comma separated ownerName",
description =
"Returns true if the change event entity being accessed has following owners from the List.",
examples = {"matchAnyOwnerName({'Owner1', 'Owner2'})"},
paramInputType = SPECIFIC_INDEX_ELASTIC_SEARCH)
public boolean matchAnyOwnerName(List ownerNameList) {
if (changeEvent == null || changeEvent.getEntity() == null) {
return false;
}
// Filter does not apply to Thread Change Events
if (changeEvent.getEntityType().equals(THREAD)) {
return true;
}
EntityInterface entity = getEntity(changeEvent);
List ownerReferences = entity.getOwners();
if (nullOrEmpty(ownerReferences)) {
entity =
Entity.getEntity(
changeEvent.getEntityType(), entity.getId(), "owner", Include.NON_DELETED);
ownerReferences = entity.getOwners();
}
if (!nullOrEmpty(ownerReferences)) {
for (EntityReference owner : ownerReferences) {
if (USER.equals(owner.getType())) {
User user = Entity.getEntity(Entity.USER, owner.getId(), "", Include.NON_DELETED);
for (String name : ownerNameList) {
if (user.getName().equals(name)) {
return true;
}
}
} else if (TEAM.equals(owner.getType())) {
Team team = Entity.getEntity(Entity.TEAM, owner.getId(), "", Include.NON_DELETED);
for (String name : ownerNameList) {
if (team.getName().equals(name)) {
return true;
}
}
}
}
}
return false;
}
@Function(
name = "matchAnyEntityFqn",
input = "List of comma separated entityName",
description =
"Returns true if the change event entity being accessed has following entityName from the List.",
examples = {"matchAnyEntityFqn({'FQN1', 'FQN2'})"},
paramInputType = ALL_INDEX_ELASTIC_SEARCH)
public boolean matchAnyEntityFqn(List entityNames) {
if (changeEvent == null || changeEvent.getEntity() == null) {
return false;
}
// Filter does not apply to Thread Change Events
if (changeEvent.getEntityType().equals(THREAD)) {
return true;
}
EntityInterface entity = getEntity(changeEvent);
for (String name : entityNames) {
Pattern pattern = Pattern.compile(name);
Matcher matcher = pattern.matcher(entity.getFullyQualifiedName());
if (matcher.find()) {
return true;
}
}
return false;
}
@Function(
name = "matchAnyEntityId",
input = "List of comma separated entity Ids",
description =
"Returns true if the change event entity being accessed has following entityId from the List.",
examples = {"matchAnyEntityId({'uuid1', 'uuid2'})"},
paramInputType = ALL_INDEX_ELASTIC_SEARCH)
public boolean matchAnyEntityId(List entityIds) {
if (changeEvent == null || changeEvent.getEntity() == null) {
return false;
}
// Filter does not apply to Thread Change Events
if (changeEvent.getEntityType().equals(THREAD)) {
return true;
}
EntityInterface entity = getEntity(changeEvent);
for (String id : entityIds) {
if (entity.getId().equals(UUID.fromString(id))) {
return true;
}
}
return false;
}
@Function(
name = "matchAnyEventType",
input = "List of comma separated eventTypes",
description =
"Returns true if the change event entity being accessed has following entityId from the List.",
examples = {
"matchAnyEventType('entityCreated', 'entityUpdated', 'entityDeleted', 'entitySoftDeleted')"
},
paramInputType = READ_FROM_PARAM_CONTEXT)
public boolean matchAnyEventType(List eventTypesList) {
if (changeEvent == null || changeEvent.getEventType() == null) {
return false;
}
String eventType = changeEvent.getEventType().toString();
for (String type : eventTypesList) {
if (eventType.equals(type)) {
return true;
}
}
return false;
}
@Function(
name = "matchTestResult",
input = "List of comma separated eventTypes",
description =
"Returns true if the change event entity being accessed has following entityId from the List.",
examples = {"matchTestResult({'Success', 'Failed', 'Aborted'})"},
paramInputType = READ_FROM_PARAM_CONTEXT)
public boolean matchTestResult(List testResults) {
if (changeEvent == null || changeEvent.getChangeDescription() == null) {
return false;
}
if (!changeEvent.getEntityType().equals(TEST_CASE)) {
// in case the entity is not test case return since the filter doesn't apply
return true;
}
// we need to handle both fields updated and fields added
List fieldChanges = changeEvent.getChangeDescription().getFieldsUpdated();
if (!changeEvent.getChangeDescription().getFieldsAdded().isEmpty()) {
fieldChanges.addAll(changeEvent.getChangeDescription().getFieldsAdded());
}
for (FieldChange fieldChange : fieldChanges) {
if (fieldChange.getName().equals("testCaseResult") && fieldChange.getNewValue() != null) {
TestCaseResult testCaseResult =
JsonUtils.readOrConvertValue(fieldChange.getNewValue(), TestCaseResult.class);
TestCaseStatus status = testCaseResult.getTestCaseStatus();
for (String givenStatus : testResults) {
if (givenStatus.equalsIgnoreCase(status.value())) {
return true;
}
}
}
}
return false;
}
@Function(
name = "filterByTableNameTestCaseBelongsTo",
input = "List of comma separated Test Suite",
description =
"Returns true if the change event entity being accessed has following entityId from the List.",
examples = {"filterByTableNameTestCaseBelongsTo({'tableName1', 'tableName2'})"},
paramInputType = READ_FROM_PARAM_CONTEXT)
public boolean filterByTableNameTestCaseBelongsTo(List tableNameList) {
if (changeEvent == null) {
return false;
}
if (!changeEvent.getEntityType().equals(TEST_CASE)) {
// in case the entity is not test case return since the filter doesn't apply
return true;
}
// Filter does not apply to Thread Change Events
if (changeEvent.getEntityType().equals(THREAD)) {
return true;
}
EntityInterface entity = getEntity(changeEvent);
for (String name : tableNameList) {
// Escape regex special characters in table name for exact matching
String escapedName = Pattern.quote(name);
// Construct regex to match table name exactly, allowing for end of string or delimiter (.)
String regex = "\\b" + escapedName + "(\\b|\\.|$)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(entity.getFullyQualifiedName());
if (matcher.find()) {
return true;
}
}
return false;
}
@Function(
name = "getTestCaseStatusIfInTestSuite",
input = "List of comma separated Test Suite",
description =
"Returns true if the change event entity being accessed has following entityId from the List.",
examples = {
"getTestCaseStatusIfInTestSuite({'testSuite1','testSuite2'}, {'Success', 'Failed', 'Aborted'})"
},
paramInputType = READ_FROM_PARAM_CONTEXT)
public boolean getTestCaseStatusIfInTestSuite(
List testResults, List testSuiteList) {
if (changeEvent == null || changeEvent.getChangeDescription() == null) {
return false;
}
if (!changeEvent.getEntityType().equals(TEST_CASE)) {
// in case the entity is not test case return since the filter doesn't apply
return true;
}
// Filter does not apply to Thread Change Events
if (changeEvent.getEntityType().equals(THREAD)) {
return true;
}
// we need to handle both fields updated and fields added
EntityInterface entity = getEntity(changeEvent);
TestCase entityWithTestSuite =
Entity.getEntity(
changeEvent.getEntityType(), entity.getId(), "testSuites", Include.NON_DELETED);
boolean testSuiteFiltering =
listOrEmpty(entityWithTestSuite.getTestSuites()).stream()
.anyMatch(
testSuite ->
testSuiteList.stream()
.anyMatch(name -> testSuite.getFullyQualifiedName().equals(name)));
if (testSuiteFiltering) {
return matchTestResult(testResults);
}
return false;
}
@Function(
name = "matchUpdatedBy",
input = "List of comma separated user names that updated the entity",
description = "Returns true if the change event entity is updated by the mentioned users",
examples = {"matchUpdatedBy({'user1', 'user2'})"},
paramInputType = READ_FROM_PARAM_CONTEXT)
public boolean matchUpdatedBy(List updatedByUserList) {
if (changeEvent == null || changeEvent.getUserName() == null) {
return false;
}
String entityUpdatedBy = changeEvent.getUserName();
for (String name : updatedByUserList) {
if (name.equals(entityUpdatedBy)) {
return true;
}
}
return false;
}
@Function(
name = "matchIngestionPipelineState",
input = "List of comma separated ingestion pipeline states",
description =
"Returns true if the change event entity being accessed has following entityId from the List.",
examples = {
"matchIngestionPipelineState({'queued', 'success', 'failed', 'running', 'partialSuccess'})"
},
paramInputType = READ_FROM_PARAM_CONTEXT)
public boolean matchIngestionPipelineState(List pipelineState) {
if (changeEvent == null || changeEvent.getChangeDescription() == null) {
return false;
}
if (!changeEvent.getEntityType().equals(INGESTION_PIPELINE)) {
// in case the entity is not ingestion pipeline return since the filter doesn't apply
return true;
}
// Filter does not apply to Thread Change Events
if (changeEvent.getEntityType().equals(THREAD)) {
return true;
}
for (FieldChange fieldChange : changeEvent.getChangeDescription().getFieldsUpdated()) {
if (fieldChange.getName().equals("pipelineStatus") && fieldChange.getNewValue() != null) {
PipelineStatus pipelineStatus =
JsonUtils.readOrConvertValue(fieldChange.getNewValue(), PipelineStatus.class);
PipelineStatusType status = pipelineStatus.getPipelineState();
for (String givenStatus : pipelineState) {
if (givenStatus.equalsIgnoreCase(status.value())) {
return true;
}
}
}
}
return false;
}
@Function(
name = "matchPipelineState",
input = "List of comma separated pipeline states",
description =
"Returns true if the change event entity being accessed has following entityId from the List.",
examples = {"matchPipelineState({'Successful', 'Failed', 'Pending', 'Skipped'})"},
paramInputType = READ_FROM_PARAM_CONTEXT)
public boolean matchPipelineState(List pipelineState) {
if (changeEvent == null || changeEvent.getChangeDescription() == null) {
return false;
}
if (!changeEvent.getEntityType().equals(PIPELINE)) {
// in case the entity is not ingestion pipeline return since the filter doesn't apply
return true;
}
// Filter does not apply to Thread Change Events
if (changeEvent.getEntityType().equals(THREAD)) {
return true;
}
for (FieldChange fieldChange : changeEvent.getChangeDescription().getFieldsUpdated()) {
if (fieldChange.getName().equals("pipelineStatus") && fieldChange.getNewValue() != null) {
org.openmetadata.schema.entity.data.PipelineStatus pipelineStatus =
JsonUtils.convertValue(
fieldChange.getNewValue(),
org.openmetadata.schema.entity.data.PipelineStatus.class);
StatusType status = pipelineStatus.getExecutionStatus();
for (String givenStatus : pipelineState) {
if (givenStatus.equalsIgnoreCase(status.value())) {
return true;
}
}
}
}
return false;
}
@Function(
name = "matchAnyFieldChange",
input = "List of comma separated fields change",
description = "Returns true if the change event entity is updated by the mentioned users",
examples = {"matchAnyFieldChange({'fieldName1', 'fieldName'})"},
paramInputType = READ_FROM_PARAM_CONTEXT_PER_ENTITY)
public boolean matchAnyFieldChange(List fieldChangeUpdate) {
if (changeEvent == null || changeEvent.getChangeDescription() == null) {
return false;
}
Set fields = FormatterUtil.getUpdatedField(changeEvent);
for (String name : fieldChangeUpdate) {
if (fields.contains(name)) {
return true;
}
}
return false;
}
@Function(
name = "matchAnyDomain",
input = "List of comma separated Domains",
description = "Returns true if the change event entity belongs to a domain from the list",
examples = {"matchAnyDomain({'domain1', 'domain2'})"},
paramInputType = SPECIFIC_INDEX_ELASTIC_SEARCH)
public boolean matchAnyDomain(List fieldChangeUpdate) {
if (changeEvent == null) {
return false;
}
// Filter does not apply to Thread Change Events
if (changeEvent.getEntityType().equals(THREAD)) {
return true;
}
// Filter does not apply to Thread Change Events
if (changeEvent.getEntityType().equals(THREAD)) {
return true;
}
EntityInterface entity = getEntity(changeEvent);
EntityInterface entityWithDomainData =
Entity.getEntity(
changeEvent.getEntityType(), entity.getId(), "domain", Include.NON_DELETED);
if (entityWithDomainData.getDomain() != null) {
for (String name : fieldChangeUpdate) {
if (entityWithDomainData.getDomain().getFullyQualifiedName().equals(name)) {
return true;
}
}
}
return false;
}
public static EntityInterface getEntity(ChangeEvent event) {
Class extends EntityInterface> entityClass =
Entity.getEntityClassFromType(event.getEntityType());
if (entityClass != null) {
EntityInterface entity;
if (event.getEntity() instanceof String str) {
entity = JsonUtils.readValue(str, entityClass);
} else {
entity = JsonUtils.convertValue(event.getEntity(), entityClass);
}
return entity;
}
throw new IllegalArgumentException(
String.format(
"Change Event Data Asset is not an entity %s",
JsonUtils.pojoToJson(event.getEntity())));
}
@Function(
name = "matchConversationUser",
input = "List of comma separated user names to matchConversationUser",
description = "Returns true if the conversation mentions the user names in the list",
examples = {"matchConversationUser({'user1', 'user2'})"},
paramInputType = READ_FROM_PARAM_CONTEXT)
public boolean matchConversationUser(List usersOrTeamName) {
if (changeEvent == null || changeEvent.getEntityType() == null) {
return false;
}
// Filter does not apply to Thread Change Events
if (!changeEvent.getEntityType().equals(THREAD)) {
return false;
}
if (usersOrTeamName.size() == 1 && usersOrTeamName.get(0).equals("all")) {
return true;
}
Thread thread = getThread(changeEvent);
if (!thread.getType().equals(Conversation)) {
// Only applies to Conversation
return false;
}
List mentions;
if (thread.getPostsCount() == 0) {
mentions = MessageParser.getEntityLinks(thread.getMessage());
} else {
Post latestPost = thread.getPosts().get(thread.getPostsCount() - 1);
mentions = MessageParser.getEntityLinks(latestPost.getMessage());
}
for (MessageParser.EntityLink entityLink : mentions) {
String fqn = entityLink.getEntityFQN();
if (USER.equals(entityLink.getEntityType())) {
User user = Entity.getCollectionDAO().userDAO().findEntityByName(fqn);
if (usersOrTeamName.contains(user.getName())) {
return true;
}
} else if (TEAM.equals(entityLink.getEntityType())) {
Team team = Entity.getCollectionDAO().teamDAO().findEntityByName(fqn);
if (usersOrTeamName.contains(team.getName())) {
return true;
}
}
}
return false;
}
public static Thread getThread(ChangeEvent event) {
try {
Thread thread;
if (event.getEntity() instanceof String str) {
thread = JsonUtils.readValue(str, Thread.class);
} else {
thread = JsonUtils.convertValue(event.getEntity(), Thread.class);
}
return thread;
} catch (Exception ex) {
throw new IllegalArgumentException(
String.format(
"Change Event Data Asset is not an Thread %s",
JsonUtils.pojoToJson(event.getEntity())));
}
}
}