diff --git a/ingestion/src/metadata/data_quality/interface/pandas/pandas_test_suite_interface.py b/ingestion/src/metadata/data_quality/interface/pandas/pandas_test_suite_interface.py index 10d6c7d12c8..e6d1a199c36 100644 --- a/ingestion/src/metadata/data_quality/interface/pandas/pandas_test_suite_interface.py +++ b/ingestion/src/metadata/data_quality/interface/pandas/pandas_test_suite_interface.py @@ -93,7 +93,7 @@ class PandasTestSuiteInterface(TestSuiteInterface, PandasInterfaceMixin): test_handler = TestHandler( self.dfs, test_case=test_case, - execution_date=datetime.now(tz=timezone.utc).timestamp(), + execution_date=int(datetime.now(tz=timezone.utc).timestamp() * 1000), ) return Validator(validator_obj=test_handler).validate() diff --git a/ingestion/src/metadata/data_quality/interface/sqlalchemy/sqa_test_suite_interface.py b/ingestion/src/metadata/data_quality/interface/sqlalchemy/sqa_test_suite_interface.py index 8da479a2dab..9413625cfaa 100644 --- a/ingestion/src/metadata/data_quality/interface/sqlalchemy/sqa_test_suite_interface.py +++ b/ingestion/src/metadata/data_quality/interface/sqlalchemy/sqa_test_suite_interface.py @@ -165,7 +165,7 @@ class SQATestSuiteInterface(SQAInterfaceMixin, TestSuiteInterface): test_handler = TestHandler( self.runner, test_case=test_case, - execution_date=datetime.now(tz=timezone.utc).timestamp(), + execution_date=int(datetime.now(tz=timezone.utc).timestamp() * 1000), ) return Validator(validator_obj=test_handler).validate() diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java index 5ad19eb7b50..9c7fe0fbe4c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseRepository.java @@ -241,27 +241,51 @@ public class TestCaseRepository extends EntityRepository { String.format("Failed to find testCase result for %s at %s", testCase.getName(), timestamp)); } - private void setTestSuiteSummary(TestCase testCase, Long timestamp, TestCaseStatus testCaseStatus) { - ResultSummary resultSummary = - new ResultSummary() - .withTestCaseName(testCase.getFullyQualifiedName()) - .withStatus(testCaseStatus) - .withTimestamp(timestamp); - EntityReference ref = testCase.getTestSuite(); - TestSuite testSuite = Entity.getEntity(ref.getType(), ref.getId(), "", Include.ALL, false); - List resultSummaries = listOrEmpty(testSuite.getTestCaseResultSummary()); - if (resultSummaries.isEmpty()) { - resultSummaries.add(resultSummary); - } else { - // We'll remove the existing summary for this test case and add the new one - resultSummaries.removeIf(summary -> summary.getTestCaseName().equals(resultSummary.getTestCaseName())); - resultSummaries.add(resultSummary); - } + private ResultSummary getResultSummary(TestCase testCase, Long timestamp, TestCaseStatus testCaseStatus) { + return new ResultSummary() + .withTestCaseName(testCase.getFullyQualifiedName()) + .withStatus(testCaseStatus) + .withTimestamp(timestamp); + } - testSuite.setTestCaseResultSummary(resultSummaries); - daoCollection - .testSuiteDAO() - .update(testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite)); + private void setTestSuiteSummary(TestCase testCase, Long timestamp, TestCaseStatus testCaseStatus) { + ResultSummary resultSummary = getResultSummary(testCase, timestamp, testCaseStatus); + + // list all executable and logical test suite linked to the test case + // We'll only fetch the logical ones as we'll get the executable one from the + // test case object itself + List testSuites = new ArrayList<>(); + List entityRelationshipRecords = + daoCollection + .relationshipDAO() + .findFrom(testCase.getId().toString(), TEST_CASE, Relationship.CONTAINS.ordinal(), TEST_SUITE); + for (CollectionDAO.EntityRelationshipRecord entityRelationshipRecord : entityRelationshipRecords) { + TestSuite testSuite = Entity.getEntity(TEST_SUITE, entityRelationshipRecord.getId(), "", Include.ALL, false); + if (Boolean.FALSE.equals(testSuite.getExecutable())) { + testSuites.add(testSuite); + } + } + EntityReference ref = testCase.getTestSuite(); + testSuites.add(Entity.getEntity(ref.getType(), ref.getId(), "", Include.ALL, false)); + + // update the summary for each test suite + for (TestSuite testSuite : testSuites) { + List resultSummaries = listOrEmpty(testSuite.getTestCaseResultSummary()); + if (resultSummaries.isEmpty()) { + resultSummaries.add(resultSummary); + } else { + // We'll remove the existing summary for this test case and add the new one + resultSummaries.removeIf(summary -> summary.getTestCaseName().equals(resultSummary.getTestCaseName())); + resultSummaries.add(resultSummary); + } + + // set test case result summary for the test suite + // and update it in the database + testSuite.setTestCaseResultSummary(resultSummaries); + daoCollection + .testSuiteDAO() + .update(testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite)); + } } private ChangeDescription addTestCaseChangeDescription(Double version, Object newValue) { @@ -330,8 +354,21 @@ public class TestCaseRepository extends EntityRepository { public RestUtil.PutResponse addTestCasesToLogicalTestSuite(TestSuite testSuite, List testCaseIds) { bulkAddToRelationship(testSuite.getId(), testCaseIds, TEST_SUITE, TEST_CASE, Relationship.CONTAINS); List testCasesEntityReferences = new ArrayList<>(); + List resultSummaries = listOrEmpty(testSuite.getTestCaseResultSummary()); for (UUID testCaseId : testCaseIds) { TestCase testCase = Entity.getEntity(Entity.TEST_CASE, testCaseId, "", Include.ALL); + // Get the latest result to set the testSuite summary field + String result = + daoCollection + .dataQualityDataTimeSeriesDao() + .getLatestExtension(testCase.getFullyQualifiedName(), TESTCASE_RESULT_EXTENSION); + if (result != null) { + TestCaseResult testCaseResult = JsonUtils.readValue(result, TestCaseResult.class); + ResultSummary resultSummary = + getResultSummary(testCase, testCaseResult.getTimestamp(), testCaseResult.getTestCaseStatus()); + resultSummaries.removeIf(summary -> summary.getTestCaseName().equals(resultSummary.getTestCaseName())); + resultSummaries.add(resultSummary); + } testCasesEntityReferences.add( new EntityReference() .withId(testCase.getId()) @@ -342,6 +379,13 @@ public class TestCaseRepository extends EntityRepository { .withHref(testCase.getHref()) .withDeleted(testCase.getDeleted())); } + // set test case result summary for logical test suite + // and update it in the database + testSuite.setTestCaseResultSummary(resultSummaries); + daoCollection + .testSuiteDAO() + .update(testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite)); + testSuite.setTests(testCasesEntityReferences); return new RestUtil.PutResponse<>(Response.Status.OK, testSuite, LOGICAL_TEST_CASES_ADDED); } @@ -349,6 +393,14 @@ public class TestCaseRepository extends EntityRepository { public RestUtil.DeleteResponse deleteTestCaseFromLogicalTestSuite(UUID testSuiteId, UUID testCaseId) { TestCase testCase = Entity.getEntity(Entity.TEST_CASE, testCaseId, null, null); deleteRelationship(testSuiteId, TEST_SUITE, testCaseId, TEST_CASE, Relationship.CONTAINS); + // remove test case from logical test suite summary and update test suite + TestSuite testSuite = Entity.getEntity(TEST_SUITE, testSuiteId, "*", Include.ALL, false); + List resultSummaries = testSuite.getTestCaseResultSummary(); + resultSummaries.removeIf(summary -> summary.getTestCaseName().equals(testCase.getFullyQualifiedName())); + testSuite.setTestCaseResultSummary(resultSummaries); + daoCollection + .testSuiteDAO() + .update(testSuite.getId(), testSuite.getFullyQualifiedName(), JsonUtils.pojoToJson(testSuite)); EntityReference entityReference = Entity.getEntityReferenceById(TEST_SUITE, testSuiteId, Include.ALL); testCase.setTestSuite(entityReference); return new RestUtil.DeleteResponse<>(testCase, RestUtil.ENTITY_DELETED); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java index 2483a4bb38f..d3047b899bc 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestSuiteRepository.java @@ -58,6 +58,7 @@ public class TestSuiteRepository extends EntityRepository { } private TestSummary buildTestSummary(HashMap testCaseSummary, int total) { + return new TestSummary() .withAborted(testCaseSummary.getOrDefault(TestCaseStatus.Aborted.toString(), 0)) .withFailed(testCaseSummary.getOrDefault(TestCaseStatus.Failed.toString(), 0)) @@ -115,13 +116,10 @@ public class TestSuiteRepository extends EntityRepository { List testSuites = listAll(EntityUtil.Fields.EMPTY_FIELDS, filter); testSummary = getTestCasesExecutionSummary(testSuites); } else { - TestSuite testSuite = find(testSuiteId, Include.ALL); - if (!Boolean.TRUE.equals(testSuite.getExecutable())) { - throw new IllegalArgumentException("Test Suite is not executable. Please provide an executable test suite."); - } + // don't want to get it from the cache as test results summary may be stale + TestSuite testSuite = Entity.getEntity(TEST_SUITE, testSuiteId, "", Include.ALL, false); testSummary = getTestCasesExecutionSummary(testSuite); } - return testSummary; } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java index 9d951cae879..9929ef86b4d 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseResource.java @@ -621,7 +621,9 @@ public class TestCaseResource extends EntityResource getTestSummary(ADMIN_AUTH_HEADERS, randomUUID), NOT_FOUND, "testSuite instance for " + randomUUID + " not found"); + + // Test that we can get the test summary for a logical test suite and that + // adding a logical test suite does not change the total number of tests + TestSuiteResourceTest testSuiteResourceTest = new TestSuiteResourceTest(); + CreateTestSuite createLogicalTestSuite = testSuiteResourceTest.createRequest(test); + TestSuite logicalTestSuite = testSuiteResourceTest.createEntity(createLogicalTestSuite, ADMIN_AUTH_HEADERS); + List testCaseIds = new ArrayList<>(); + testCaseIds.add(testCase1.getId()); + testSuiteResourceTest.addTestCasesToLogicalTestSuite(logicalTestSuite, testCaseIds); + + testSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString()); + assertEquals(1, testSummary.getTotal()); + assertEquals(1, testSummary.getFailed()); + + // add a new test case to the logical test suite to validate if the + // summary is updated correctly + testCaseIds.removeAll(testCaseIds); + testCaseIds.add(testCase.getId()); + testSuiteResourceTest.addTestCasesToLogicalTestSuite(logicalTestSuite, testCaseIds); + + testSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString()); + assertEquals(2, testSummary.getTotal()); + + // remove test case from logical test suite and validate + // the summary is updated as expected + deleteLogicalTestCase(logicalTestSuite, testCase.getId()); + + testSummary = getTestSummary(ADMIN_AUTH_HEADERS, logicalTestSuite.getId().toString()); + assertEquals(1, testSummary.getTotal()); } @Test