diff --git a/bootstrap/sql/migrations/native/1.11.0/mysql/schemaChanges.sql b/bootstrap/sql/migrations/native/1.11.0/mysql/schemaChanges.sql index ebfd739e2ee..04adef45524 100644 --- a/bootstrap/sql/migrations/native/1.11.0/mysql/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.11.0/mysql/schemaChanges.sql @@ -7,10 +7,12 @@ CREATE TABLE IF NOT EXISTS test_case_dimension_results_time_series ( id VARCHAR(36) GENERATED ALWAYS AS (json_unquote(json_extract(json,'$.id'))) STORED NOT NULL, testCaseResultId VARCHAR(36) GENERATED ALWAYS AS (json_unquote(json_extract(json,'$.testCaseResultId'))) STORED NOT NULL, dimensionKey VARCHAR(512) GENERATED ALWAYS AS (json_unquote(json_extract(json,'$.dimensionKey'))) STORED NOT NULL, + dimensionName VARCHAR(256) GENERATED ALWAYS AS (SUBSTRING_INDEX(json_unquote(json_extract(json,'$.dimensionKey')), '=', 1)) STORED, timestamp BIGINT UNSIGNED GENERATED ALWAYS AS (json_unquote(json_extract(json,'$.timestamp'))) STORED NOT NULL, testCaseStatus VARCHAR(36) GENERATED ALWAYS AS (json_unquote(json_extract(json,'$.testCaseStatus'))) STORED, UNIQUE KEY test_case_dimension_results_unique_constraint (entityFQNHash, dimensionKey, timestamp), INDEX test_case_dimension_results_main (entityFQNHash, timestamp, dimensionKey), + INDEX test_case_dimension_results_dimension_name (entityFQNHash, dimensionName, timestamp), INDEX test_case_dimension_results_result_id (testCaseResultId), INDEX test_case_dimension_results_ts (timestamp) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; diff --git a/bootstrap/sql/migrations/native/1.11.0/postgres/schemaChanges.sql b/bootstrap/sql/migrations/native/1.11.0/postgres/schemaChanges.sql index a038bcfe29a..a86d1fb3efa 100644 --- a/bootstrap/sql/migrations/native/1.11.0/postgres/schemaChanges.sql +++ b/bootstrap/sql/migrations/native/1.11.0/postgres/schemaChanges.sql @@ -7,6 +7,7 @@ CREATE TABLE IF NOT EXISTS test_case_dimension_results_time_series ( id VARCHAR(36) GENERATED ALWAYS AS (json ->> 'id') STORED NOT NULL, testCaseResultId VARCHAR(36) GENERATED ALWAYS AS (json ->> 'testCaseResultId') STORED NOT NULL, dimensionKey VARCHAR(512) GENERATED ALWAYS AS (json ->> 'dimensionKey') STORED NOT NULL, + dimensionName VARCHAR(256) GENERATED ALWAYS AS (SPLIT_PART(json ->> 'dimensionKey', '=', 1)) STORED, timestamp BIGINT GENERATED ALWAYS AS ((json ->> 'timestamp')::bigint) STORED NOT NULL, testCaseStatus VARCHAR(36) GENERATED ALWAYS AS (json ->> 'testCaseStatus') STORED, CONSTRAINT test_case_dimension_results_unique_constraint UNIQUE (entityFQNHash, dimensionKey, timestamp) @@ -14,6 +15,7 @@ CREATE TABLE IF NOT EXISTS test_case_dimension_results_time_series ( -- Create indexes CREATE INDEX IF NOT EXISTS test_case_dimension_results_main ON test_case_dimension_results_time_series (entityFQNHash, timestamp, dimensionKey); +CREATE INDEX IF NOT EXISTS test_case_dimension_results_dimension_name ON test_case_dimension_results_time_series (entityFQNHash, dimensionName, timestamp); CREATE INDEX IF NOT EXISTS test_case_dimension_results_result_id ON test_case_dimension_results_time_series (testCaseResultId); CREATE INDEX IF NOT EXISTS test_case_dimension_results_ts ON test_case_dimension_results_time_series (timestamp); -- Add impersonatedBy column to all entity tables for tracking bot impersonation diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java index 41016009b5a..7a3b5f03cc9 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java @@ -6886,6 +6886,16 @@ public interface CollectionDAO { @Bind("startTs") Long startTs, @Bind("endTs") Long endTs); + @SqlQuery( + "SELECT json FROM test_case_dimension_results_time_series " + + "WHERE entityFQNHash = :testCaseFQN AND dimensionName = :dimensionName AND timestamp >= :startTs AND timestamp <= :endTs " + + "ORDER BY timestamp DESC") + List listTestCaseDimensionResultsByDimensionName( + @BindFQN("testCaseFQN") String testCaseFQN, + @Bind("dimensionName") String dimensionName, + @Bind("startTs") Long startTs, + @Bind("endTs") Long endTs); + @SqlQuery( "SELECT DISTINCT dimensionKey FROM test_case_dimension_results_time_series " + "WHERE entityFQNHash = :testCaseFQN AND timestamp >= :startTs AND timestamp <= :endTs") diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseDimensionResultRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseDimensionResultRepository.java index d0bf3af4433..92f6958825c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseDimensionResultRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TestCaseDimensionResultRepository.java @@ -36,9 +36,14 @@ public class TestCaseDimensionResultRepository * @param startTs Start timestamp (optional) * @param endTs End timestamp (optional) * @param dimensionalityKey Optional filter by specific dimension key + * @param dimensionName Optional filter by dimension name */ public ResultList listDimensionResults( - String testCaseFQN, Long startTs, Long endTs, String dimensionalityKey) { + String testCaseFQN, + Long startTs, + Long endTs, + String dimensionalityKey, + String dimensionName) { startTs = Optional.ofNullable(startTs).orElse(Long.MIN_VALUE); endTs = Optional.ofNullable(endTs).orElse(Long.MAX_VALUE); @@ -51,6 +56,13 @@ public class TestCaseDimensionResultRepository dimensionResultDao.listTestCaseDimensionResultsByKey( testCaseFQN, dimensionalityKey, startTs, endTs), TestCaseDimensionResult.class); + } else if (dimensionName != null && !dimensionName.isEmpty()) { + // Filter by dimension name + results = + JsonUtils.readObjects( + dimensionResultDao.listTestCaseDimensionResultsByDimensionName( + testCaseFQN, dimensionName, startTs, endTs), + TestCaseDimensionResult.class); } else { // Get all dimension results results = diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseDimensionResultResource.java b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseDimensionResultResource.java index aa57265f817..6399ba98c04 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseDimensionResultResource.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/resources/dqtests/TestCaseDimensionResultResource.java @@ -67,7 +67,8 @@ public class TestCaseDimensionResultResource "Get a list of dimensional results for a specific test case. " + "Results can be filtered by time range and specific dimension values. " + "Use `startTs` and `endTs` to filter results within a time range. " - + "Use `dimensionalityKey` to filter results for a specific dimension value combination.", + + "Use `dimensionalityKey` to filter results for a specific dimension value combination. " + + "Use `dimensionName` to filter results for all values of a specific dimension (e.g., 'column' to get all column dimension results).", responses = { @ApiResponse( responseCode = "200", @@ -99,7 +100,13 @@ public class TestCaseDimensionResultResource description = "Filter by specific dimension key (e.g., 'column=address')", schema = @Schema(type = "string")) @QueryParam("dimensionalityKey") - String dimensionalityKey) + String dimensionalityKey, + @Parameter( + description = + "Filter by dimension name (e.g., 'column' to get all column dimension results)", + schema = @Schema(type = "string")) + @QueryParam("dimensionName") + String dimensionName) throws IOException { TestCase testCase = getTestCase(testCaseFQN); ResourceContextInterface testCaseResourceContext = @@ -119,7 +126,8 @@ public class TestCaseDimensionResultResource new AuthRequest(entityOperationContext, entityResourceContext)); authorizer.authorizeRequests(securityContext, authRequests, AuthorizationLogic.ANY); - return repository.listDimensionResults(testCaseFQN, startTs, endTs, dimensionalityKey); + return repository.listDimensionResults( + testCaseFQN, startTs, endTs, dimensionalityKey, dimensionName); } @GET diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java index 28b3be17f00..981bad9546b 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/resources/dqtests/TestCaseResourceTest.java @@ -4483,6 +4483,26 @@ public class TestCaseResourceTest extends EntityResourceTest columnDimensionResults = + JsonUtils.readValue( + json, + new com.fasterxml.jackson.core.type.TypeReference< + ResultList>() {}); + + assertEquals(9, columnDimensionResults.getData().size()); // All 3 days × 3 columns + assertTrue( + columnDimensionResults.getData().stream() + .allMatch(r -> r.getDimensionKey().startsWith("column="))); } @Test