GEN 1654 - Fix Alerts for Test Suites (#18222)

* fix: test suite alerts

* fix: return testSuites field for test suite alerts

* style: ran java linting
This commit is contained in:
Teddy 2024-10-11 10:26:13 +02:00 committed by GitHub
parent d0ca05efbf
commit add22b5d28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 133 additions and 25 deletions

View File

@ -15,6 +15,7 @@ import static org.openmetadata.service.Entity.THREAD;
import static org.openmetadata.service.Entity.USER; import static org.openmetadata.service.Entity.USER;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -28,6 +29,7 @@ import org.openmetadata.schema.entity.services.ingestionPipelines.PipelineStatus
import org.openmetadata.schema.entity.teams.Team; import org.openmetadata.schema.entity.teams.Team;
import org.openmetadata.schema.entity.teams.User; import org.openmetadata.schema.entity.teams.User;
import org.openmetadata.schema.tests.TestCase; import org.openmetadata.schema.tests.TestCase;
import org.openmetadata.schema.tests.TestSuite;
import org.openmetadata.schema.tests.type.TestCaseResult; import org.openmetadata.schema.tests.type.TestCaseResult;
import org.openmetadata.schema.tests.type.TestCaseStatus; import org.openmetadata.schema.tests.type.TestCaseStatus;
import org.openmetadata.schema.type.ChangeEvent; import org.openmetadata.schema.type.ChangeEvent;
@ -97,27 +99,24 @@ public class AlertsRuleEvaluator {
if (nullOrEmpty(ownerReferences)) { if (nullOrEmpty(ownerReferences)) {
entity = entity =
Entity.getEntity( Entity.getEntity(
changeEvent.getEntityType(), entity.getId(), "owner", Include.NON_DELETED); changeEvent.getEntityType(), entity.getId(), "owners", Include.NON_DELETED);
ownerReferences = entity.getOwners(); ownerReferences = entity.getOwners();
} }
if (!nullOrEmpty(ownerReferences)) { if (!nullOrEmpty(ownerReferences)) {
for (EntityReference owner : ownerReferences) { return matchOwners(ownerReferences, ownerNameList);
if (USER.equals(owner.getType())) { }
User user = Entity.getEntity(Entity.USER, owner.getId(), "", Include.NON_DELETED);
for (String name : ownerNameList) { if (changeEvent.getEntityType().equals(TEST_CASE)) {
if (user.getName().equals(name)) { // If we did not match on the owner name and are dealing with a test case,
return true; // check if the match happens on the test suite owner name
} TestCase testCase =
} Entity.getEntity(
} else if (TEAM.equals(owner.getType())) { changeEvent.getEntityType(),
Team team = Entity.getEntity(Entity.TEAM, owner.getId(), "", Include.NON_DELETED); entity.getId(),
for (String name : ownerNameList) { "testSuites,owners",
if (team.getName().equals(name)) { Include.NON_DELETED);
return true; Optional<List<TestSuite>> testSuites = Optional.ofNullable(testCase.getTestSuites());
} return testSuites.filter(suites -> testSuiteOwnerMatcher(suites, ownerNameList)).isPresent();
}
}
}
} }
return false; return false;
} }
@ -147,6 +146,15 @@ public class AlertsRuleEvaluator {
return true; return true;
} }
} }
if (changeEvent.getEntityType().equals(TEST_CASE)) {
// If we did not match on the entity FQN and are dealing with a test case,
// check if the match happens on the test suite FQN
TestCase testCase = ((TestCase) entity);
Optional<List<TestSuite>> testSuites = Optional.ofNullable(testCase.getTestSuites());
return testSuites.filter(suites -> testSuiteMatcher(suites, entityNames)).isPresent();
}
return false; return false;
} }
@ -455,6 +463,14 @@ public class AlertsRuleEvaluator {
} }
} }
} }
if (changeEvent.getEntityType().equals(TEST_CASE)) {
// If we did not match on the domain and are dealing with a test case,
// check if the match happens on the test suite domain
TestCase testCase = ((TestCase) entity);
Optional<List<TestSuite>> testSuites = Optional.ofNullable(testCase.getTestSuites());
return testSuites.filter(suites -> testSuiteMatcher(suites, fieldChangeUpdate)).isPresent();
}
return false; return false;
} }
@ -543,4 +559,50 @@ public class AlertsRuleEvaluator {
JsonUtils.pojoToJson(event.getEntity()))); JsonUtils.pojoToJson(event.getEntity())));
} }
} }
private boolean testSuiteMatcher(List<TestSuite> testSuites, List<String> entityNames) {
for (TestSuite testSuite : testSuites) {
for (String name : entityNames) {
Pattern pattern = Pattern.compile(name);
Matcher matcherTestSuiteFQN = pattern.matcher(testSuite.getFullyQualifiedName());
if (matcherTestSuiteFQN.find()) return true;
if (testSuite.getDomain() != null) {
Matcher matcherDomainFQN = pattern.matcher(testSuite.getDomain().getFullyQualifiedName());
if (matcherDomainFQN.find()) return true;
}
}
}
return false;
}
private boolean testSuiteOwnerMatcher(List<TestSuite> testSuites, List<String> ownerNameList) {
boolean match;
for (TestSuite testSuite : testSuites) {
List<EntityReference> owners = testSuite.getOwners();
match = matchOwners(owners, ownerNameList);
if (match) return true;
}
return false;
}
private boolean matchOwners(List<EntityReference> ownerReferences, List<String> ownerNameList) {
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;
}
} }

View File

@ -316,7 +316,10 @@ public class FormatterUtil {
JsonUtils.readOrConvertValue(entityTimeSeries, TestCaseResult.class); JsonUtils.readOrConvertValue(entityTimeSeries, TestCaseResult.class);
TestCase testCase = TestCase testCase =
Entity.getEntityByName( Entity.getEntityByName(
TEST_CASE, testCaseResult.getTestCaseFQN(), TEST_CASE_RESULT, Include.ALL); TEST_CASE,
testCaseResult.getTestCaseFQN(),
TEST_CASE_RESULT + ",testSuites",
Include.ALL);
ChangeEvent changeEvent = ChangeEvent changeEvent =
getChangeEvent(updateBy, eventType, testCase.getEntityReference().getType(), testCase); getChangeEvent(updateBy, eventType, testCase.getEntityReference().getType(), testCase);
return changeEvent return changeEvent

View File

@ -206,7 +206,8 @@ public class TestCaseRepository extends EntityRepository<TestCase> {
return records.stream() return records.stream()
.map( .map(
testSuiteId -> testSuiteId ->
Entity.<TestSuite>getEntity(TEST_SUITE, testSuiteId.getId(), "", Include.ALL, false) Entity.<TestSuite>getEntity(
TEST_SUITE, testSuiteId.getId(), "owners,domain", Include.ALL, false)
.withInherited(true) .withInherited(true)
.withChangeDescription(null)) .withChangeDescription(null))
.toList(); .toList();

View File

@ -14,7 +14,9 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInfo;
import org.openmetadata.schema.api.data.CreateTable; import org.openmetadata.schema.api.data.CreateTable;
import org.openmetadata.schema.api.tests.CreateTestCase;
import org.openmetadata.schema.entity.data.Table; import org.openmetadata.schema.entity.data.Table;
import org.openmetadata.schema.tests.TestCase;
import org.openmetadata.schema.tests.type.TestCaseResult; import org.openmetadata.schema.tests.type.TestCaseResult;
import org.openmetadata.schema.tests.type.TestCaseStatus; import org.openmetadata.schema.tests.type.TestCaseStatus;
import org.openmetadata.schema.type.ChangeDescription; import org.openmetadata.schema.type.ChangeDescription;
@ -23,20 +25,24 @@ import org.openmetadata.schema.type.Column;
import org.openmetadata.schema.type.ColumnDataType; import org.openmetadata.schema.type.ColumnDataType;
import org.openmetadata.schema.type.EventType; import org.openmetadata.schema.type.EventType;
import org.openmetadata.schema.type.FieldChange; import org.openmetadata.schema.type.FieldChange;
import org.openmetadata.schema.type.Include;
import org.openmetadata.service.Entity; import org.openmetadata.service.Entity;
import org.openmetadata.service.OpenMetadataApplicationTest; import org.openmetadata.service.OpenMetadataApplicationTest;
import org.openmetadata.service.resources.EntityResourceTest; import org.openmetadata.service.resources.EntityResourceTest;
import org.openmetadata.service.resources.databases.TableResourceTest; import org.openmetadata.service.resources.databases.TableResourceTest;
import org.openmetadata.service.resources.dqtests.TestCaseResourceTest;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.SimpleEvaluationContext; import org.springframework.expression.spel.support.SimpleEvaluationContext;
class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest { class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest {
private static TableResourceTest tableResourceTest; private static TableResourceTest tableResourceTest;
private static TestCaseResourceTest testCaseResourceTest;
@BeforeAll @BeforeAll
public static void setup(TestInfo test) throws URISyntaxException, IOException { public static void setup(TestInfo test) throws URISyntaxException, IOException {
tableResourceTest = new TableResourceTest(); tableResourceTest = new TableResourceTest();
tableResourceTest.setup(test); tableResourceTest.setup(test);
testCaseResourceTest = new TestCaseResourceTest();
} }
@Test @Test
@ -47,6 +53,7 @@ class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest {
AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent);
EvaluationContext evaluationContext = EvaluationContext evaluationContext =
SimpleEvaluationContext.forReadOnlyDataBinding() SimpleEvaluationContext.forReadOnlyDataBinding()
.withInstanceMethods()
.withRootObject(alertsRuleEvaluator) .withRootObject(alertsRuleEvaluator)
.build(); .build();
assertTrue(evaluateExpression("matchAnySource('alert')", evaluationContext)); assertTrue(evaluateExpression("matchAnySource('alert')", evaluationContext));
@ -73,6 +80,7 @@ class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest {
AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent);
EvaluationContext evaluationContext = EvaluationContext evaluationContext =
SimpleEvaluationContext.forReadOnlyDataBinding() SimpleEvaluationContext.forReadOnlyDataBinding()
.withInstanceMethods()
.withRootObject(alertsRuleEvaluator) .withRootObject(alertsRuleEvaluator)
.build(); .build();
assertTrue( assertTrue(
@ -81,12 +89,25 @@ class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest {
assertFalse(evaluateExpression("matchAnyOwnerName('tempName')", evaluationContext)); assertFalse(evaluateExpression("matchAnyOwnerName('tempName')", evaluationContext));
} }
// issue: https://github.com/open-metadata/OpenMetadata/issues/10376 @Test
void test_matchAnyEntityFqn(TestInfo test) throws IOException { void test_matchAnyEntityFqn() throws IOException {
// Create Table Entity // Create Table Entity
// SpEl parsing fails for non-basic UTF-8
// https://github.com/open-metadata/OpenMetadata/issues/10376
List<Column> columns = List.of(TableResourceTest.getColumn(C1, ColumnDataType.INT, null)); List<Column> columns = List.of(TableResourceTest.getColumn(C1, ColumnDataType.INT, null));
CreateTable create = tableResourceTest.createRequest(test).withColumns(columns); CreateTable create = tableResourceTest.createRequest("table").withColumns(columns);
Table createdTable = tableResourceTest.createAndCheckEntity(create, ADMIN_AUTH_HEADERS); Table createdTable = tableResourceTest.createAndCheckEntity(create, ADMIN_AUTH_HEADERS);
createdTable.setFullyQualifiedName(
"ServiceName.DatabaseName.SchemaName." + createdTable.getName());
CreateTestCase createTestCase = testCaseResourceTest.createRequest("testCase");
TestCase testCase =
testCaseResourceTest.createAndCheckEntity(createTestCase, ADMIN_AUTH_HEADERS);
testCase = Entity.getEntity(testCase.getEntityReference(), "testSuites", Include.ALL);
testCase.setFullyQualifiedName(
"ServiceName.DatabaseName.SchemaName.table." + testCase.getName());
String testSuiteFqn =
"ServiceName.DatabaseName.SchemaName.table." + testCase.getName() + ".testSuite";
testCase.getTestSuites().get(0).setFullyQualifiedName(testSuiteFqn);
// Create a change Event with the Entity Table // Create a change Event with the Entity Table
ChangeEvent changeEvent = new ChangeEvent(); ChangeEvent changeEvent = new ChangeEvent();
@ -97,11 +118,27 @@ class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest {
AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent);
EvaluationContext evaluationContext = EvaluationContext evaluationContext =
SimpleEvaluationContext.forReadOnlyDataBinding() SimpleEvaluationContext.forReadOnlyDataBinding()
.withInstanceMethods()
.withRootObject(alertsRuleEvaluator) .withRootObject(alertsRuleEvaluator)
.build(); .build();
String fqn = createdTable.getFullyQualifiedName(); String fqn = createdTable.getFullyQualifiedName();
assertTrue(evaluateExpression("matchAnyEntityFqn('" + fqn + "')", evaluationContext)); assertTrue(evaluateExpression("matchAnyEntityFqn({'" + fqn + "'})", evaluationContext));
assertFalse(evaluateExpression("matchAnyEntityFqn('testFQN1')", evaluationContext)); assertFalse(evaluateExpression("(matchAnyEntityFqn({'FOO'}))", evaluationContext));
// Create a change Event with the Entity Test Case
changeEvent = new ChangeEvent();
changeEvent.setEntityType(Entity.TEST_CASE);
changeEvent.setEntity(testCase);
// Test Entity FQN match for test case
alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent);
evaluationContext =
SimpleEvaluationContext.forReadOnlyDataBinding()
.withInstanceMethods()
.withRootObject(alertsRuleEvaluator)
.build();
assertTrue(
evaluateExpression("matchAnyEntityFqn({'" + testSuiteFqn + "'})", evaluationContext));
assertFalse(evaluateExpression("(matchAnyEntityFqn({'FOO'}))", evaluationContext));
} }
@Test @Test
@ -120,6 +157,7 @@ class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest {
AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent);
EvaluationContext evaluationContext = EvaluationContext evaluationContext =
SimpleEvaluationContext.forReadOnlyDataBinding() SimpleEvaluationContext.forReadOnlyDataBinding()
.withInstanceMethods()
.withRootObject(alertsRuleEvaluator) .withRootObject(alertsRuleEvaluator)
.build(); .build();
String id = createdTable.getId().toString(); String id = createdTable.getId().toString();
@ -138,6 +176,7 @@ class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest {
AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent);
EvaluationContext evaluationContext = EvaluationContext evaluationContext =
SimpleEvaluationContext.forReadOnlyDataBinding() SimpleEvaluationContext.forReadOnlyDataBinding()
.withInstanceMethods()
.withRootObject(alertsRuleEvaluator) .withRootObject(alertsRuleEvaluator)
.build(); .build();
assertTrue(evaluateExpression("matchAnyEventType('entityCreated')", evaluationContext)); assertTrue(evaluateExpression("matchAnyEventType('entityCreated')", evaluationContext));
@ -164,6 +203,7 @@ class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest {
AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent);
EvaluationContext evaluationContext = EvaluationContext evaluationContext =
SimpleEvaluationContext.forReadOnlyDataBinding() SimpleEvaluationContext.forReadOnlyDataBinding()
.withInstanceMethods()
.withRootObject(alertsRuleEvaluator) .withRootObject(alertsRuleEvaluator)
.build(); .build();
assertTrue(evaluateExpression("matchTestResult('Success')", evaluationContext)); assertTrue(evaluateExpression("matchTestResult('Success')", evaluationContext));
@ -180,6 +220,7 @@ class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest {
AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent);
EvaluationContext evaluationContext = EvaluationContext evaluationContext =
SimpleEvaluationContext.forReadOnlyDataBinding() SimpleEvaluationContext.forReadOnlyDataBinding()
.withInstanceMethods()
.withRootObject(alertsRuleEvaluator) .withRootObject(alertsRuleEvaluator)
.build(); .build();
assertTrue(evaluateExpression("matchUpdatedBy('test')", evaluationContext)); assertTrue(evaluateExpression("matchUpdatedBy('test')", evaluationContext));
@ -200,6 +241,7 @@ class AlertsRuleEvaluatorResourceTest extends OpenMetadataApplicationTest {
AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent); AlertsRuleEvaluator alertsRuleEvaluator = new AlertsRuleEvaluator(changeEvent);
EvaluationContext evaluationContext = EvaluationContext evaluationContext =
SimpleEvaluationContext.forReadOnlyDataBinding() SimpleEvaluationContext.forReadOnlyDataBinding()
.withInstanceMethods()
.withRootObject(alertsRuleEvaluator) .withRootObject(alertsRuleEvaluator)
.build(); .build();
assertTrue(evaluateExpression("matchAnyFieldChange('test')", evaluationContext)); assertTrue(evaluateExpression("matchAnyFieldChange('test')", evaluationContext));