diff --git a/ingestion/src/metadata/utils/constants.py b/ingestion/src/metadata/utils/constants.py index 2228d199a0f6..049fd6b151c8 100644 --- a/ingestion/src/metadata/utils/constants.py +++ b/ingestion/src/metadata/utils/constants.py @@ -21,6 +21,7 @@ from metadata.generated.schema.entity.data.dashboardDataModel import DashboardDataModel from metadata.generated.schema.entity.data.database import Database from metadata.generated.schema.entity.data.databaseSchema import DatabaseSchema +from metadata.generated.schema.entity.data.dataContract import DataContract from metadata.generated.schema.entity.data.directory import Directory from metadata.generated.schema.entity.data.file import File from metadata.generated.schema.entity.data.glossary import Glossary @@ -168,6 +169,7 @@ "metric": Metric, "glossary": Glossary, "glossaryTerm": GlossaryTerm, + "dataContract": DataContract, "eventSubscription": EventSubscription, } diff --git a/ingestion/src/metadata/utils/elasticsearch.py b/ingestion/src/metadata/utils/elasticsearch.py index 7e50d1c82fdd..fac971a30a5a 100644 --- a/ingestion/src/metadata/utils/elasticsearch.py +++ b/ingestion/src/metadata/utils/elasticsearch.py @@ -29,6 +29,7 @@ from metadata.generated.schema.entity.data.dashboardDataModel import DashboardDataModel from metadata.generated.schema.entity.data.database import Database from metadata.generated.schema.entity.data.databaseSchema import DatabaseSchema +from metadata.generated.schema.entity.data.dataContract import DataContract from metadata.generated.schema.entity.data.directory import Directory from metadata.generated.schema.entity.data.file import File from metadata.generated.schema.entity.data.glossary import Glossary @@ -80,6 +81,7 @@ Spreadsheet.__name__: "spreadsheet_search_index", Worksheet.__name__: "worksheet_search_index", Query.__name__: "query_search_index", + DataContract.__name__: "data_contract_search_index", ReportData.__name__: "entity_report_data_index", Metric.__name__: "metric_search_index", "web_analytic_user_activity_report": "web_analytic_user_activity_report_data_index", diff --git a/ingestion/tests/unit/test_ometa_utils.py b/ingestion/tests/unit/test_ometa_utils.py index 854c160eeb67..34db1cf998b5 100644 --- a/ingestion/tests/unit/test_ometa_utils.py +++ b/ingestion/tests/unit/test_ometa_utils.py @@ -25,6 +25,7 @@ from metadata.generated.schema.entity.data.dashboardDataModel import DashboardDataModel from metadata.generated.schema.entity.data.database import Database from metadata.generated.schema.entity.data.databaseSchema import DatabaseSchema +from metadata.generated.schema.entity.data.dataContract import DataContract from metadata.generated.schema.entity.data.directory import Directory from metadata.generated.schema.entity.data.mlmodel import MlModel from metadata.generated.schema.entity.data.pipeline import Pipeline @@ -58,6 +59,7 @@ model_str, ) from metadata.utils.constants import ENTITY_REFERENCE_CLASS_MAP +from metadata.utils.elasticsearch import ES_INDEX_MAP MOCK_TABLE = Table( id="c3eb265f-5445-4ad3-ba5e-797d3a3071bb", @@ -100,6 +102,12 @@ def test_get_entity_type(self): self.assertEqual(get_entity_type("hello"), "hello") self.assertEqual(get_entity_type(MlModel), "mlmodel") + def test_get_entity_type_data_contract(self): + self.assertEqual(get_entity_type(DataContract), "dataContract") + + def test_es_index_map_data_contract(self): + self.assertEqual(ES_INDEX_MAP[DataContract.__name__], "data_contract_search_index") + def test_get_entity_type_camel_case_consistency(self): """ Test that get_entity_type returns camelCase entity types diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DataContractResourceIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DataContractResourceIT.java index 49819fe30d1b..fb2b6cda8e67 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DataContractResourceIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DataContractResourceIT.java @@ -53,6 +53,7 @@ import org.openmetadata.schema.entity.services.ingestionPipelines.PipelineStatus; import org.openmetadata.schema.entity.services.ingestionPipelines.PipelineStatusType; import org.openmetadata.schema.entity.teams.Role; +import org.openmetadata.schema.entity.teams.User; import org.openmetadata.schema.tests.TestCase; import org.openmetadata.schema.tests.TestCaseParameterValue; import org.openmetadata.schema.tests.TestDefinition; @@ -100,7 +101,7 @@ public class DataContractResourceIT extends BaseEntityIT { + String response = searchForEntity(contract.getId().toString()); + assertTrue(response.contains(ownerRef.getId().toString()), "owner id indexed"); + assertTrue(response.contains(assetFqn), "entity.fullyQualifiedName indexed"); + assertTrue(response.contains("\"type\":\"table\""), "entity.type indexed"); + }); + } + + @Test + void addResult_keepsOwnersInSearchDoc(TestNamespace ns) throws Exception { + DataContract contract = createEntity(createMinimalRequest(ns)); + String ownerName = ("dc_result_owner_" + ns.uniqueShortId()).toLowerCase(Locale.ROOT); + User owner = + SdkClients.adminClient() + .users() + .create( + new CreateUser() + .withName(ownerName) + .withEmail(ownerName + "@test.openmetadata.org")); + String ownerId = owner.getId().toString(); + contract.setOwners(List.of(owner.getEntityReference())); + SdkClients.adminClient().dataContracts().update(contract.getId().toString(), contract); + + String marker = "keep-owners-" + ns.uniqueShortId(); + SdkClients.adminClient() + .dataContracts() + .addResult( + contract.getId(), + new DataContractResult() + .withDataContractFQN(contract.getFullyQualifiedName()) + .withTimestamp(System.currentTimeMillis()) + .withContractExecutionStatus(ContractExecutionStatus.Failed) + .withResult(marker) + .withExecutionTime(1L)); + + Awaitility.await("contract doc reflects the new result") + .pollInterval(Duration.ofSeconds(2)) + .atMost(Duration.ofSeconds(120)) + .ignoreExceptions() + .untilAsserted( + () -> assertTrue(searchForEntity(contract.getId().toString()).contains(marker))); + assertTrue( + searchForEntity(contract.getId().toString()).contains(ownerId), + "recording a result must not drop owners from the search doc"); + } } diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataContractRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataContractRepository.java index 22ef2060dfb6..a1853e118e1c 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataContractRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DataContractRepository.java @@ -128,6 +128,7 @@ public DataContractRepository(OpenMetadataApplicationConfig config) { DATA_CONTRACT_UPDATE_FIELDS); this.ingestionPipelineMapper = new IngestionPipelineMapper(config); this.openMetadataApplicationConfig = config; + supportsSearch = true; } @Override @@ -1596,7 +1597,15 @@ private boolean semanticsRuleMatch(SemanticsRule rule1, SemanticsRule rule2) { private void updateLatestResult(DataContract dataContract, DataContractResult result) { try { - DataContract updated = JsonUtils.deepCopy(dataContract, DataContract.class); + // Reload with the relationship fields the search doc is built from: callers pass contracts + // fetched without owners/reviewers, and the reindex after this update would blank them. + DataContract original = + Entity.getEntity( + Entity.DATA_CONTRACT, + dataContract.getId(), + "owners,reviewers,extension", + Include.NON_DELETED); + DataContract updated = JsonUtils.deepCopy(original, DataContract.class); updated.setLatestResult( new LatestResult() .withTimestamp(result.getTimestamp()) @@ -1604,7 +1613,7 @@ private void updateLatestResult(DataContract dataContract, DataContractResult re .withMessage(result.getResult()) .withResultId(result.getId())); EntityRepository.EntityUpdater entityUpdater = - getUpdater(dataContract, updated, EntityRepository.Operation.PATCH, null); + getUpdater(original, updated, EntityRepository.Operation.PATCH, null); entityUpdater.update(); } catch (Exception e) { LOG.error( diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchIndexFactory.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchIndexFactory.java index 2f18298b8595..30ec57427a8e 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchIndexFactory.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/SearchIndexFactory.java @@ -21,6 +21,7 @@ import org.openmetadata.schema.entity.data.ContextFile; import org.openmetadata.schema.entity.data.Dashboard; import org.openmetadata.schema.entity.data.DashboardDataModel; +import org.openmetadata.schema.entity.data.DataContract; import org.openmetadata.schema.entity.data.Database; import org.openmetadata.schema.entity.data.DatabaseSchema; import org.openmetadata.schema.entity.data.Directory; @@ -79,6 +80,7 @@ import org.openmetadata.service.search.indexes.DashboardDataModelIndex; import org.openmetadata.service.search.indexes.DashboardIndex; import org.openmetadata.service.search.indexes.DashboardServiceIndex; +import org.openmetadata.service.search.indexes.DataContractIndex; import org.openmetadata.service.search.indexes.DataProductIndex; import org.openmetadata.service.search.indexes.DatabaseIndex; import org.openmetadata.service.search.indexes.DatabaseSchemaIndex; @@ -215,6 +217,7 @@ public SearchIndex buildIndex(String entityType, Object entity) { case Entity.CONTEXT_FILE -> new ContextFileIndex((ContextFile) entity); case Entity.CONTEXT_MEMORY -> new ContextMemoryIndex((ContextMemory) entity); case Entity.PAGE -> new PageIndex((Page) entity); + case Entity.DATA_CONTRACT -> new DataContractIndex((DataContract) entity); case Entity.DATA_PRODUCT -> new DataProductIndex((DataProduct) entity); case Entity.METADATA_SERVICE -> new MetadataServiceIndex((MetadataService) entity); case Entity.ENTITY_REPORT_DATA -> new EntityReportDataIndex((ReportData) entity); diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/search/indexes/DataContractIndex.java b/openmetadata-service/src/main/java/org/openmetadata/service/search/indexes/DataContractIndex.java new file mode 100644 index 000000000000..c007801b6b42 --- /dev/null +++ b/openmetadata-service/src/main/java/org/openmetadata/service/search/indexes/DataContractIndex.java @@ -0,0 +1,47 @@ +package org.openmetadata.service.search.indexes; + +import java.util.Map; +import java.util.Set; +import org.openmetadata.schema.entity.data.DataContract; +import org.openmetadata.service.Entity; + +public record DataContractIndex(DataContract dataContract) implements SearchIndex { + /** Large or free-form contract blocks; indexing them would explode the dynamic mapping. */ + private static final Set EXCLUDED_FIELDS = + Set.of( + "schema", + "semantics", + "qualityExpectations", + "odcsQualityRules", + "odcsElementExtensions", + "contractUpdates", + "sla", + "security", + "termsOfUse"); + + @Override + public Object getEntity() { + return dataContract; + } + + @Override + public String getEntityTypeName() { + return Entity.DATA_CONTRACT; + } + + @Override + public Map buildSearchIndexDocInternal(Map doc) { + doc.put("entity", getEntityWithDisplayName(dataContract.getEntity())); + doc.put("testSuite", getEntityWithDisplayName(dataContract.getTestSuite())); + return doc; + } + + @Override + public Set getExcludedFields() { + return EXCLUDED_FIELDS; + } + + public static Map getFields() { + return SearchIndex.getDefaultFields(); + } +} diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/search/DataContractIndexMappingTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/search/DataContractIndexMappingTest.java new file mode 100644 index 000000000000..7618e2b4ad3c --- /dev/null +++ b/openmetadata-service/src/test/java/org/openmetadata/service/search/DataContractIndexMappingTest.java @@ -0,0 +1,107 @@ +package org.openmetadata.service.search; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DataContractIndexMappingTest { + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final List FREE_FORM_FIELDS = + List.of( + "schema", + "semantics", + "qualityExpectations", + "odcsQualityRules", + "odcsElementExtensions", + "contractUpdates", + "sla", + "security", + "termsOfUse"); + + private static JsonNode read(String path) throws IOException { + try (InputStream in = DataContractIndexMappingTest.class.getResourceAsStream(path)) { + assertNotNull(in, "missing resource " + path); + return MAPPER.readTree(in); + } + } + + private static JsonNode properties(String language) throws IOException { + return read("/elasticsearch/" + language + "/data_contract_index_mapping.json") + .path("mappings") + .path("properties"); + } + + @Test + void indexIsRegisteredWithoutParentAliases() throws IOException { + JsonNode entry = read("/elasticsearch/indexMapping.json").path("dataContract"); + assertEquals("data_contract_search_index", entry.path("indexName").asText()); + assertEquals( + "/elasticsearch/%s/data_contract_index_mapping.json", + entry.path("indexMappingFile").asText()); + assertEquals("dataContract", entry.path("alias").asText()); + // Not under `all`: owner/team asset lists and the domain/tag asset pickers query `all`, and a + // contract there would be listed with no link and accepted as a domain/tag asset it can't hold. + assertTrue(entry.path("parentAliases").isEmpty()); + assertTrue(entry.path("childAliases").isEmpty()); + } + + @ParameterizedTest + @ValueSource(strings = {"en", "jp", "ru", "zh"}) + void contractFieldsAreMapped(String language) throws IOException { + JsonNode props = properties(language); + assertEquals("keyword", props.at("/entity/properties/type/type").asText()); + assertEquals( + "keyword", props.at("/entity/properties/fullyQualifiedName/fields/keyword/type").asText()); + assertEquals("keyword", props.at("/testSuite/properties/id/type").asText()); + assertEquals("keyword", props.at("/latestResult/properties/status/type").asText()); + assertEquals("keyword", props.at("/reviewers/properties/id/type").asText()); + assertEquals("nested", props.at("/owners/type").asText()); + assertEquals("keyword", props.at("/fqnHash/type").asText()); + } + + @ParameterizedTest + @ValueSource(strings = {"en", "jp", "ru", "zh"}) + void entityFqnKeywordHasNoIgnoreAbove(String language) throws IOException { + JsonNode keyword = + properties(language).at("/entity/properties/fullyQualifiedName/fields/keyword"); + assertTrue( + keyword.path("ignore_above").isMissingNode(), + "asset FQNs can exceed 256 chars; ignore_above would drop them from the filter"); + } + + @ParameterizedTest + @ValueSource(strings = {"en", "jp", "ru", "zh"}) + void referenceFqnKeywordsAreLowercaseNormalized(String language) throws IOException { + // Aggregation autocomplete lowercases the typed prefix; an un-normalized keyword never matches + // mixed-case FQNs once the user types. + JsonNode props = properties(language); + for (String ref : List.of("entity", "testSuite")) { + assertEquals( + "lowercase_normalizer", + props + .at("/" + ref + "/properties/fullyQualifiedName/fields/keyword/normalizer") + .asText()); + } + } + + @ParameterizedTest + @ValueSource(strings = {"en", "jp", "ru", "zh"}) + void freeFormBlocksAndTagsAreNotMapped(String language) throws IOException { + JsonNode props = properties(language); + for (String field : FREE_FORM_FIELDS) { + assertFalse(props.has(field), field + " must be excluded, not mapped"); + } + assertFalse(props.has("tags"), "contracts have no tags"); + assertFalse(props.has("classificationTags"), "contracts have no tags"); + } +} diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchIndexFactoryTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchIndexFactoryTest.java index 3943d5cb45c9..1da5f937df42 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchIndexFactoryTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/search/SearchIndexFactoryTest.java @@ -34,6 +34,7 @@ import org.openmetadata.schema.entity.data.Container; import org.openmetadata.schema.entity.data.Dashboard; import org.openmetadata.schema.entity.data.DashboardDataModel; +import org.openmetadata.schema.entity.data.DataContract; import org.openmetadata.schema.entity.data.Database; import org.openmetadata.schema.entity.data.DatabaseSchema; import org.openmetadata.schema.entity.data.Directory; @@ -91,6 +92,7 @@ import org.openmetadata.service.search.indexes.DashboardDataModelIndex; import org.openmetadata.service.search.indexes.DashboardIndex; import org.openmetadata.service.search.indexes.DashboardServiceIndex; +import org.openmetadata.service.search.indexes.DataContractIndex; import org.openmetadata.service.search.indexes.DataProductIndex; import org.openmetadata.service.search.indexes.DatabaseIndex; import org.openmetadata.service.search.indexes.DatabaseSchemaIndex; @@ -416,6 +418,8 @@ private static Stream supportedIndexMappings() { AiFrameworkControlIndex.class), Arguments.of( Entity.AUDIT_REPORT, (Supplier) AuditReport::new, AuditReportIndex.class), + Arguments.of( + Entity.DATA_CONTRACT, (Supplier) DataContract::new, DataContractIndex.class), Arguments.of(Entity.TAG, (Supplier) Tag::new, TagIndex.class), Arguments.of( Entity.CLASSIFICATION, diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/search/indexes/DataContractIndexTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/search/indexes/DataContractIndexTest.java new file mode 100644 index 000000000000..6f4c289b577a --- /dev/null +++ b/openmetadata-service/src/test/java/org/openmetadata/service/search/indexes/DataContractIndexTest.java @@ -0,0 +1,129 @@ +package org.openmetadata.service.search.indexes; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.InputStream; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.openmetadata.schema.api.data.ContractSLA; +import org.openmetadata.schema.api.data.ContractSecurity; +import org.openmetadata.schema.entity.data.DataContract; +import org.openmetadata.schema.entity.data.TermsOfUse; +import org.openmetadata.schema.type.EntityReference; +import org.openmetadata.service.Entity; +import org.openmetadata.service.search.SearchRepository; + +class DataContractIndexTest { + private static final List FREE_FORM_FIELDS = + List.of( + "schema", + "semantics", + "qualityExpectations", + "odcsQualityRules", + "odcsElementExtensions", + "contractUpdates", + "sla", + "security", + "termsOfUse"); + + private static SearchRepository previousRepository; + + @BeforeAll + static void initSearchRepository() { + previousRepository = Entity.getSearchRepository(); + Entity.setSearchRepository(mock(SearchRepository.class)); + } + + @AfterAll + static void restoreSearchRepository() { + Entity.setSearchRepository(previousRepository); + } + + private static EntityReference ref(String type, String name) { + return new EntityReference() + .withId(UUID.randomUUID()) + .withType(type) + .withName(name) + .withFullyQualifiedName("svc.db.schema." + name); + } + + private static DataContract contract() { + return new DataContract() + .withId(UUID.randomUUID()) + .withName("orders_contract") + .withFullyQualifiedName("svc.db.schema.orders.dataContract_orders_contract") + .withEntity(ref(Entity.TABLE, "orders")) + .withOwners(List.of(ref(Entity.USER, "alice"))); + } + + @Test + void buildDoc_setsEntityReferenceWithDisplayName() { + Map doc = new DataContractIndex(contract()).buildSearchIndexDoc(); + + EntityReference entity = (EntityReference) doc.get("entity"); + assertEquals(Entity.TABLE, entity.getType()); + assertEquals("orders", entity.getDisplayName(), "displayName falls back to name"); + assertEquals(Entity.DATA_CONTRACT, doc.get("entityType")); + assertEquals(List.of("alice"), doc.get("ownerName")); + } + + @Test + void buildDoc_minimalContract_hasNoNulls() { + DataContract minimal = + new DataContract() + .withId(UUID.randomUUID()) + .withName("c") + .withFullyQualifiedName("svc.db.schema.t.dataContract_c") + .withEntity(ref(Entity.TABLE, "t")); + + Map doc = new DataContractIndex(minimal).buildSearchIndexDoc(); + + assertNull(doc.get("testSuite")); + assertEquals(List.of(), doc.get("owners")); + assertEquals(List.of(), doc.get("reviewers")); + } + + @Test + void buildDoc_excludesFreeFormBlocks() { + DataContract heavy = + contract() + .withSla(new ContractSLA()) + .withSecurity(new ContractSecurity()) + .withTermsOfUse(new TermsOfUse().withContent("terms")); + + Map doc = new DataContractIndex(heavy).buildSearchIndexDoc(); + + for (String field : FREE_FORM_FIELDS) { + assertFalse(doc.containsKey(field), field + " must not reach the search doc"); + } + } + + @Test + void everyDocKeyIsMapped() throws Exception { + JsonNode properties; + try (InputStream in = + getClass().getResourceAsStream("/elasticsearch/en/data_contract_index_mapping.json")) { + properties = new ObjectMapper().readTree(in).path("mappings").path("properties"); + } + Set mapped = new HashSet<>(); + properties.fieldNames().forEachRemaining(mapped::add); + + Map doc = new DataContractIndex(contract()).buildSearchIndexDoc(); + + Set unmapped = new HashSet<>(doc.keySet()); + unmapped.removeAll(mapped); + assertTrue(unmapped.isEmpty(), "doc keys missing from mapping (dynamic mapping): " + unmapped); + } +} diff --git a/openmetadata-service/src/test/java/org/openmetadata/service/search/indexes/EntityTypeNameTest.java b/openmetadata-service/src/test/java/org/openmetadata/service/search/indexes/EntityTypeNameTest.java index 351107bafde1..eeb7d517b29d 100644 --- a/openmetadata-service/src/test/java/org/openmetadata/service/search/indexes/EntityTypeNameTest.java +++ b/openmetadata-service/src/test/java/org/openmetadata/service/search/indexes/EntityTypeNameTest.java @@ -28,6 +28,7 @@ import org.openmetadata.schema.entity.data.Container; import org.openmetadata.schema.entity.data.Dashboard; import org.openmetadata.schema.entity.data.DashboardDataModel; +import org.openmetadata.schema.entity.data.DataContract; import org.openmetadata.schema.entity.data.Database; import org.openmetadata.schema.entity.data.DatabaseSchema; import org.openmetadata.schema.entity.data.Directory; @@ -120,6 +121,7 @@ static Stream indexEntityTypeProvider() { new AiFrameworkControlIndex(new AIFrameworkControl().withId(id())), Entity.AI_FRAMEWORK_CONTROL), Arguments.of(new AuditReportIndex(new AuditReport().withId(id())), Entity.AUDIT_REPORT), + Arguments.of(new DataContractIndex(new DataContract().withId(id())), Entity.DATA_CONTRACT), Arguments.of(new McpServerIndex(new McpServer().withId(id())), Entity.MCP_SERVER), Arguments.of( new PromptTemplateIndex(new PromptTemplate().withId(id())), Entity.PROMPT_TEMPLATE), diff --git a/openmetadata-spec/src/main/resources/elasticsearch/en/data_contract_index_mapping.json b/openmetadata-spec/src/main/resources/elasticsearch/en/data_contract_index_mapping.json new file mode 100644 index 000000000000..bbd4beb3e16d --- /dev/null +++ b/openmetadata-spec/src/main/resources/elasticsearch/en/data_contract_index_mapping.json @@ -0,0 +1,599 @@ +{ + "settings": { + "index": { + "max_ngram_diff": 17 + }, + "analysis": { + "tokenizer": { + "n_gram_tokenizer": { + "type": "ngram", + "min_gram": 3, + "max_gram": 20, + "token_chars": [ + "letter", + "digit" + ] + } + }, + "normalizer": { + "lowercase_normalizer": { + "type": "custom", + "char_filter": [], + "filter": [ + "lowercase", + "asciifolding" + ] + } + }, + "analyzer": { + "om_analyzer": { + "tokenizer": "standard", + "filter": [ + "word_delimiter_filter", + "lowercase", + "asciifolding", + "om_stemmer", + "om_plural_stemmer" + ] + }, + "om_ngram": { + "type": "custom", + "tokenizer": "n_gram_tokenizer", + "filter": [ + "lowercase", + "asciifolding" + ] + }, + "om_compound_analyzer": { + "tokenizer": "standard", + "filter": [ + "compound_word_delimiter_graph", + "lowercase", + "asciifolding", + "flatten_graph" + ] + } + }, + "filter": { + "om_stemmer": { + "type": "stemmer", + "name": "kstem" + }, + "word_delimiter_filter": { + "type": "word_delimiter", + "preserve_original": true + }, + "compound_word_delimiter_graph": { + "type": "word_delimiter_graph", + "generate_word_parts": true, + "generate_number_parts": true, + "split_on_case_change": true, + "split_on_numerics": true, + "catenate_words": false, + "catenate_numbers": false, + "catenate_all": false, + "preserve_original": true, + "stem_english_possessive": true + }, + "om_plural_stemmer": { + "type": "stemmer", + "name": "minimal_english" + } + } + } + }, + "mappings": { + "properties": { + "changeDescription": { + "enabled": false + }, + "incrementalChangeDescription": { + "enabled": false + }, + "id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "name": { + "type": "text", + "analyzer": "om_analyzer", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256, + "normalizer": "lowercase_normalizer" + }, + "ngram": { + "type": "text", + "analyzer": "om_ngram" + }, + "compound": { + "type": "text", + "analyzer": "om_compound_analyzer" + } + } + }, + "fullyQualifiedName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "displayName": { + "type": "text", + "analyzer": "om_analyzer", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + }, + "actualCase": { + "type": "keyword", + "ignore_above": 256 + }, + "ngram": { + "type": "text", + "analyzer": "om_ngram" + }, + "compound": { + "type": "text", + "analyzer": "om_compound_analyzer" + } + } + }, + "description": { + "type": "text", + "analyzer": "om_analyzer", + "similarity": "boolean", + "term_vector": "with_positions_offsets" + }, + "fqnParts": { + "type": "keyword" + }, + "version": { + "type": "float" + }, + "updatedAt": { + "type": "date", + "format": "epoch_second" + }, + "updatedBy": { + "type": "text" + }, + "href": { + "type": "text" + }, + "sourceUrl": { + "type": "text" + }, + "domains": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "extension": { + "type": "object", + "enabled": false + }, + "owners": { + "type": "nested", + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "deleted": { + "type": "boolean" + }, + "followers": { + "type": "keyword" + }, + "entityType": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "entityStatus": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "totalVotes": { + "type": "long", + "null_value": 0 + }, + "votes": { + "type": "object", + "dynamic": false, + "properties": { + "upVotes": { + "type": "integer" + }, + "downVotes": { + "type": "integer" + } + } + }, + "descriptionStatus": { + "type": "keyword" + }, + "descriptionSources": { + "type": "object", + "dynamic": false + }, + "certification": { + "type": "object", + "properties": { + "tagLabel": { + "type": "object", + "properties": { + "tagFQN": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "fields": { + "text": { + "type": "text", + "analyzer": "om_analyzer" + } + } + }, + "labelType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "source": { + "type": "keyword" + }, + "state": { + "type": "keyword" + } + } + }, + "appliedDate": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "expiryDate": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + } + } + }, + "customPropertiesTyped": { + "type": "nested", + "properties": { + "name": { + "type": "keyword" + }, + "propertyType": { + "type": "keyword" + }, + "stringValue": { + "type": "keyword" + }, + "textValue": { + "type": "text", + "analyzer": "om_analyzer" + }, + "longValue": { + "type": "long" + }, + "doubleValue": { + "type": "double" + }, + "start": { + "type": "long" + }, + "end": { + "type": "long" + }, + "refId": { + "type": "keyword" + }, + "refType": { + "type": "keyword" + }, + "refName": { + "type": "keyword" + }, + "refFqn": { + "type": "keyword" + } + } + }, + "ownerDisplayName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "ownerName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "fqnHash": { + "type": "keyword" + }, + "impersonatedBy": { + "type": "keyword" + }, + "reviewers": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "entity": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + } + } + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "testSuite": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + } + } + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "latestResult": { + "properties": { + "status": { + "type": "keyword" + }, + "timestamp": { + "type": "date", + "format": "epoch_millis" + }, + "resultId": { + "type": "keyword" + }, + "message": { + "type": "text" + } + } + }, + "effectiveFrom": { + "type": "date" + }, + "effectiveUntil": { + "type": "date" + }, + "createdAt": { + "type": "date", + "format": "epoch_millis" + }, + "createdBy": { + "type": "keyword" + }, + "inherited": { + "type": "boolean" + } + } + } +} diff --git a/openmetadata-spec/src/main/resources/elasticsearch/indexMapping.json b/openmetadata-spec/src/main/resources/elasticsearch/indexMapping.json index 8b913fb1b192..aa958c4d872d 100644 --- a/openmetadata-spec/src/main/resources/elasticsearch/indexMapping.json +++ b/openmetadata-spec/src/main/resources/elasticsearch/indexMapping.json @@ -601,6 +601,13 @@ ], "childAliases": [] }, + "dataContract": { + "indexName": "data_contract_search_index", + "indexMappingFile": "/elasticsearch/%s/data_contract_index_mapping.json", + "alias": "dataContract", + "parentAliases": [], + "childAliases": [] + }, "mcpServer": { "indexName": "mcp_server_search_index", "indexMappingFile": "/elasticsearch/%s/mcp_server_index_mapping.json", diff --git a/openmetadata-spec/src/main/resources/elasticsearch/jp/data_contract_index_mapping.json b/openmetadata-spec/src/main/resources/elasticsearch/jp/data_contract_index_mapping.json new file mode 100644 index 000000000000..21b325b1d869 --- /dev/null +++ b/openmetadata-spec/src/main/resources/elasticsearch/jp/data_contract_index_mapping.json @@ -0,0 +1,604 @@ +{ + "settings": { + "analysis": { + "normalizer": { + "lowercase_normalizer": { + "type": "custom", + "char_filter": [], + "filter": [ + "lowercase", + "asciifolding" + ] + } + }, + "analyzer": { + "om_analyzer": { + "tokenizer": "letter", + "filter": [ + "lowercase", + "asciifolding", + "om_stemmer", + "om_plural_stemmer" + ] + }, + "om_analyzer_jp": { + "tokenizer": "kuromoji_tokenizer", + "type": "custom", + "filter": [ + "kuromoji_baseform", + "kuromoji_part_of_speech", + "kuromoji_number", + "kuromoji_stemmer" + ] + }, + "om_ngram": { + "type": "custom", + "tokenizer": "n_gram_tokenizer", + "filter": [ + "lowercase", + "asciifolding" + ] + }, + "om_compound_analyzer": { + "tokenizer": "standard", + "filter": [ + "compound_word_delimiter_graph", + "lowercase", + "asciifolding", + "flatten_graph" + ] + } + }, + "filter": { + "om_stemmer": { + "type": "stemmer", + "name": "english" + }, + "compound_word_delimiter_graph": { + "type": "word_delimiter_graph", + "generate_word_parts": true, + "generate_number_parts": true, + "split_on_case_change": true, + "split_on_numerics": true, + "catenate_words": false, + "catenate_numbers": false, + "catenate_all": false, + "preserve_original": true, + "stem_english_possessive": true + }, + "om_plural_stemmer": { + "type": "stemmer", + "name": "minimal_english" + } + }, + "tokenizer": { + "n_gram_tokenizer": { + "type": "ngram", + "min_gram": 1, + "max_gram": 2, + "token_chars": [ + "letter", + "digit" + ] + } + } + }, + "index": { + "max_ngram_diff": 1 + } + }, + "mappings": { + "properties": { + "changeDescription": { + "enabled": false + }, + "incrementalChangeDescription": { + "enabled": false + }, + "id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "name": { + "type": "text", + "analyzer": "om_analyzer_jp", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256, + "normalizer": "lowercase_normalizer" + }, + "ngram": { + "type": "text", + "analyzer": "om_ngram" + }, + "compound": { + "type": "text", + "analyzer": "om_compound_analyzer" + } + } + }, + "fullyQualifiedName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "displayName": { + "type": "text", + "analyzer": "om_analyzer_jp", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + }, + "actualCase": { + "type": "keyword", + "ignore_above": 256 + }, + "ngram": { + "type": "text", + "analyzer": "om_ngram" + }, + "compound": { + "type": "text", + "analyzer": "om_compound_analyzer" + } + } + }, + "description": { + "type": "text", + "analyzer": "om_analyzer_jp", + "similarity": "boolean", + "term_vector": "with_positions_offsets" + }, + "fqnParts": { + "type": "keyword" + }, + "version": { + "type": "float" + }, + "updatedAt": { + "type": "date", + "format": "epoch_second" + }, + "updatedBy": { + "type": "text" + }, + "href": { + "type": "text" + }, + "sourceUrl": { + "type": "text" + }, + "domains": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "extension": { + "type": "object", + "enabled": false + }, + "owners": { + "type": "nested", + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "deleted": { + "type": "boolean" + }, + "followers": { + "type": "keyword" + }, + "entityType": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "entityStatus": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "totalVotes": { + "type": "long", + "null_value": 0 + }, + "votes": { + "type": "object", + "dynamic": false, + "properties": { + "upVotes": { + "type": "integer" + }, + "downVotes": { + "type": "integer" + } + } + }, + "descriptionStatus": { + "type": "keyword" + }, + "descriptionSources": { + "type": "object", + "dynamic": false + }, + "certification": { + "type": "object", + "properties": { + "tagLabel": { + "type": "object", + "properties": { + "tagFQN": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "fields": { + "text": { + "type": "text", + "analyzer": "om_analyzer" + } + } + }, + "labelType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "source": { + "type": "keyword" + }, + "state": { + "type": "keyword" + } + } + }, + "appliedDate": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "expiryDate": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + } + } + }, + "customPropertiesTyped": { + "type": "nested", + "properties": { + "name": { + "type": "keyword" + }, + "propertyType": { + "type": "keyword" + }, + "stringValue": { + "type": "keyword" + }, + "textValue": { + "type": "text", + "analyzer": "om_analyzer" + }, + "longValue": { + "type": "long" + }, + "doubleValue": { + "type": "double" + }, + "start": { + "type": "long" + }, + "end": { + "type": "long" + }, + "refId": { + "type": "keyword" + }, + "refType": { + "type": "keyword" + }, + "refName": { + "type": "keyword" + }, + "refFqn": { + "type": "keyword" + } + } + }, + "ownerDisplayName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "ownerName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "fqnHash": { + "type": "keyword" + }, + "impersonatedBy": { + "type": "keyword" + }, + "reviewers": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "entity": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + } + } + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "testSuite": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + } + } + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "latestResult": { + "properties": { + "status": { + "type": "keyword" + }, + "timestamp": { + "type": "date", + "format": "epoch_millis" + }, + "resultId": { + "type": "keyword" + }, + "message": { + "type": "text" + } + } + }, + "effectiveFrom": { + "type": "date" + }, + "effectiveUntil": { + "type": "date" + }, + "createdAt": { + "type": "date", + "format": "epoch_millis" + }, + "createdBy": { + "type": "keyword" + }, + "inherited": { + "type": "boolean" + } + } + } +} diff --git a/openmetadata-spec/src/main/resources/elasticsearch/ru/data_contract_index_mapping.json b/openmetadata-spec/src/main/resources/elasticsearch/ru/data_contract_index_mapping.json new file mode 100644 index 000000000000..ae9e6bf48458 --- /dev/null +++ b/openmetadata-spec/src/main/resources/elasticsearch/ru/data_contract_index_mapping.json @@ -0,0 +1,611 @@ +{ + "settings": { + "index": { + "max_ngram_diff": 17 + }, + "analysis": { + "tokenizer": { + "n_gram_tokenizer": { + "type": "ngram", + "min_gram": 3, + "max_gram": 20, + "token_chars": [ + "letter", + "digit" + ] + } + }, + "normalizer": { + "lowercase_normalizer": { + "type": "custom", + "char_filter": [], + "filter": [ + "lowercase", + "asciifolding" + ] + } + }, + "analyzer": { + "om_analyzer": { + "tokenizer": "standard", + "filter": [ + "word_delimiter_filter", + "lowercase", + "asciifolding", + "russian_stop", + "russian_snowball", + "english_stop", + "om_kstem" + ] + }, + "om_ngram": { + "type": "custom", + "tokenizer": "n_gram_tokenizer", + "filter": [ + "lowercase", + "asciifolding" + ] + }, + "om_compound_analyzer": { + "tokenizer": "standard", + "filter": [ + "compound_word_delimiter_graph", + "lowercase", + "asciifolding", + "flatten_graph" + ] + } + }, + "filter": { + "word_delimiter_filter": { + "type": "word_delimiter", + "preserve_original": true + }, + "compound_word_delimiter_graph": { + "type": "word_delimiter_graph", + "generate_word_parts": true, + "generate_number_parts": true, + "split_on_case_change": true, + "split_on_numerics": true, + "catenate_words": false, + "catenate_numbers": false, + "catenate_all": false, + "preserve_original": true, + "stem_english_possessive": true + }, + "russian_stop": { + "type": "stop", + "stopwords": "_russian_" + }, + "english_stop": { + "type": "stop", + "stopwords": "_english_" + }, + "russian_snowball": { + "name": "russian", + "type": "stemmer" + }, + "om_kstem": { + "type": "kstem" + }, + "asciifolding": { + "type": "asciifolding" + } + } + } + }, + "mappings": { + "properties": { + "changeDescription": { + "enabled": false + }, + "incrementalChangeDescription": { + "enabled": false + }, + "id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "name": { + "type": "text", + "analyzer": "om_analyzer", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256, + "normalizer": "lowercase_normalizer" + }, + "ngram": { + "type": "text", + "analyzer": "om_ngram" + }, + "compound": { + "type": "text", + "analyzer": "om_compound_analyzer" + } + } + }, + "fullyQualifiedName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "displayName": { + "type": "text", + "analyzer": "om_analyzer", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + }, + "actualCase": { + "type": "keyword", + "ignore_above": 256 + }, + "ngram": { + "type": "text", + "analyzer": "om_ngram" + }, + "compound": { + "type": "text", + "analyzer": "om_compound_analyzer" + } + } + }, + "description": { + "type": "text", + "analyzer": "om_analyzer", + "similarity": "boolean", + "term_vector": "with_positions_offsets" + }, + "fqnParts": { + "type": "keyword" + }, + "version": { + "type": "float" + }, + "updatedAt": { + "type": "date", + "format": "epoch_second" + }, + "updatedBy": { + "type": "text" + }, + "href": { + "type": "text" + }, + "sourceUrl": { + "type": "text" + }, + "domains": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "extension": { + "type": "object", + "enabled": false + }, + "owners": { + "type": "nested", + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "deleted": { + "type": "boolean" + }, + "followers": { + "type": "keyword" + }, + "entityType": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "entityStatus": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "totalVotes": { + "type": "long", + "null_value": 0 + }, + "votes": { + "type": "object", + "dynamic": false, + "properties": { + "upVotes": { + "type": "integer" + }, + "downVotes": { + "type": "integer" + } + } + }, + "descriptionStatus": { + "type": "keyword" + }, + "descriptionSources": { + "type": "object", + "dynamic": false + }, + "certification": { + "type": "object", + "properties": { + "tagLabel": { + "type": "object", + "properties": { + "tagFQN": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "fields": { + "text": { + "type": "text", + "analyzer": "om_analyzer" + } + } + }, + "labelType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "source": { + "type": "keyword" + }, + "state": { + "type": "keyword" + } + } + }, + "appliedDate": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "expiryDate": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + } + } + }, + "customPropertiesTyped": { + "type": "nested", + "properties": { + "name": { + "type": "keyword" + }, + "propertyType": { + "type": "keyword" + }, + "stringValue": { + "type": "keyword" + }, + "textValue": { + "type": "text", + "analyzer": "om_analyzer" + }, + "longValue": { + "type": "long" + }, + "doubleValue": { + "type": "double" + }, + "start": { + "type": "long" + }, + "end": { + "type": "long" + }, + "refId": { + "type": "keyword" + }, + "refType": { + "type": "keyword" + }, + "refName": { + "type": "keyword" + }, + "refFqn": { + "type": "keyword" + } + } + }, + "ownerDisplayName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "ownerName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "fqnHash": { + "type": "keyword" + }, + "impersonatedBy": { + "type": "keyword" + }, + "reviewers": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "entity": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + } + } + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "testSuite": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + } + } + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "latestResult": { + "properties": { + "status": { + "type": "keyword" + }, + "timestamp": { + "type": "date", + "format": "epoch_millis" + }, + "resultId": { + "type": "keyword" + }, + "message": { + "type": "text" + } + } + }, + "effectiveFrom": { + "type": "date" + }, + "effectiveUntil": { + "type": "date" + }, + "createdAt": { + "type": "date", + "format": "epoch_millis" + }, + "createdBy": { + "type": "keyword" + }, + "inherited": { + "type": "boolean" + } + } + } +} diff --git a/openmetadata-spec/src/main/resources/elasticsearch/zh/data_contract_index_mapping.json b/openmetadata-spec/src/main/resources/elasticsearch/zh/data_contract_index_mapping.json new file mode 100644 index 000000000000..6cfae1773bd7 --- /dev/null +++ b/openmetadata-spec/src/main/resources/elasticsearch/zh/data_contract_index_mapping.json @@ -0,0 +1,594 @@ +{ + "settings": { + "analysis": { + "normalizer": { + "lowercase_normalizer": { + "type": "custom", + "char_filter": [], + "filter": [ + "lowercase", + "asciifolding" + ] + } + }, + "analyzer": { + "om_analyzer": { + "tokenizer": "letter", + "filter": [ + "lowercase", + "asciifolding", + "om_stemmer", + "om_plural_stemmer" + ] + }, + "om_ngram": { + "type": "custom", + "tokenizer": "n_gram_tokenizer", + "filter": [ + "lowercase", + "asciifolding" + ] + }, + "om_compound_analyzer": { + "tokenizer": "standard", + "filter": [ + "compound_word_delimiter_graph", + "lowercase", + "asciifolding", + "flatten_graph" + ] + } + }, + "filter": { + "om_stemmer": { + "type": "stemmer", + "name": "english" + }, + "compound_word_delimiter_graph": { + "type": "word_delimiter_graph", + "generate_word_parts": true, + "generate_number_parts": true, + "split_on_case_change": true, + "split_on_numerics": true, + "catenate_words": false, + "catenate_numbers": false, + "catenate_all": false, + "preserve_original": true, + "stem_english_possessive": true + }, + "om_plural_stemmer": { + "type": "stemmer", + "name": "minimal_english" + } + }, + "tokenizer": { + "n_gram_tokenizer": { + "type": "ngram", + "min_gram": 2, + "max_gram": 3, + "token_chars": [ + "letter", + "digit" + ] + } + } + }, + "index": { + "max_ngram_diff": 1 + } + }, + "mappings": { + "properties": { + "changeDescription": { + "enabled": false + }, + "incrementalChangeDescription": { + "enabled": false + }, + "id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "name": { + "type": "text", + "analyzer": "om_analyzer", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256, + "normalizer": "lowercase_normalizer" + }, + "ngram": { + "type": "text", + "analyzer": "om_ngram" + }, + "compound": { + "type": "text", + "analyzer": "om_compound_analyzer" + } + } + }, + "fullyQualifiedName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "displayName": { + "type": "text", + "analyzer": "om_analyzer", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + }, + "actualCase": { + "type": "keyword", + "ignore_above": 256 + }, + "ngram": { + "type": "text", + "analyzer": "om_ngram" + }, + "compound": { + "type": "text", + "analyzer": "om_compound_analyzer" + } + } + }, + "description": { + "type": "text", + "analyzer": "om_analyzer", + "similarity": "boolean", + "term_vector": "with_positions_offsets" + }, + "fqnParts": { + "type": "keyword" + }, + "version": { + "type": "float" + }, + "updatedAt": { + "type": "date", + "format": "epoch_second" + }, + "updatedBy": { + "type": "text" + }, + "href": { + "type": "text" + }, + "sourceUrl": { + "type": "text" + }, + "domains": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "extension": { + "type": "object", + "enabled": false + }, + "owners": { + "type": "nested", + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "deleted": { + "type": "boolean" + }, + "followers": { + "type": "keyword" + }, + "entityType": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "entityStatus": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "totalVotes": { + "type": "long", + "null_value": 0 + }, + "votes": { + "type": "object", + "dynamic": false, + "properties": { + "upVotes": { + "type": "integer" + }, + "downVotes": { + "type": "integer" + } + } + }, + "descriptionStatus": { + "type": "keyword" + }, + "descriptionSources": { + "type": "object", + "dynamic": false + }, + "certification": { + "type": "object", + "properties": { + "tagLabel": { + "type": "object", + "properties": { + "tagFQN": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "fields": { + "text": { + "type": "text", + "analyzer": "om_analyzer" + } + } + }, + "labelType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "source": { + "type": "keyword" + }, + "state": { + "type": "keyword" + } + } + }, + "appliedDate": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + }, + "expiryDate": { + "type": "date", + "format": "strict_date_optional_time||epoch_millis" + } + } + }, + "customPropertiesTyped": { + "type": "nested", + "properties": { + "name": { + "type": "keyword" + }, + "propertyType": { + "type": "keyword" + }, + "stringValue": { + "type": "keyword" + }, + "textValue": { + "type": "text", + "analyzer": "om_analyzer" + }, + "longValue": { + "type": "long" + }, + "doubleValue": { + "type": "double" + }, + "start": { + "type": "long" + }, + "end": { + "type": "long" + }, + "refId": { + "type": "keyword" + }, + "refType": { + "type": "keyword" + }, + "refName": { + "type": "keyword" + }, + "refFqn": { + "type": "keyword" + } + } + }, + "ownerDisplayName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "ownerName": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + }, + "fqnHash": { + "type": "keyword" + }, + "impersonatedBy": { + "type": "keyword" + }, + "reviewers": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text" + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "entity": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + } + } + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "testSuite": { + "properties": { + "id": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 36 + } + } + }, + "type": { + "type": "keyword" + }, + "name": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "displayName": { + "type": "keyword", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer", + "ignore_above": 256 + } + } + }, + "fullyQualifiedName": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase_normalizer" + } + } + }, + "description": { + "type": "text" + }, + "deleted": { + "type": "boolean" + }, + "href": { + "type": "text" + } + } + }, + "latestResult": { + "properties": { + "status": { + "type": "keyword" + }, + "timestamp": { + "type": "date", + "format": "epoch_millis" + }, + "resultId": { + "type": "keyword" + }, + "message": { + "type": "text" + } + } + }, + "effectiveFrom": { + "type": "date" + }, + "effectiveUntil": { + "type": "date" + }, + "createdAt": { + "type": "date", + "format": "epoch_millis" + }, + "createdBy": { + "type": "keyword" + }, + "inherited": { + "type": "boolean" + } + } + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts b/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts index 8305e15be391..13d32559ce4b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts +++ b/openmetadata-ui/src/main/resources/ui/src/enums/AdvancedSearch.enum.ts @@ -93,6 +93,10 @@ export enum EntityFields { PARENT = 'parent.displayName.keyword', DIRECTORY = 'directory.displayName.keyword', FILE_TYPE = 'fileType', + DATA_CONTRACT_ENTITY_TYPE = 'entity.type', + DATA_CONTRACT_ENTITY_FQN = 'entity.fullyQualifiedName.keyword', + DATA_CONTRACT_LATEST_RESULT_STATUS = 'latestResult.status', + REVIEWERS = 'reviewers.displayName.keyword', FILE_EXTENSION = 'fileExtension', LAST_MODIFIED_BY = 'lastModifiedBy', SPREADSHEET = 'spreadsheet.displayName.keyword', diff --git a/openmetadata-ui/src/main/resources/ui/src/enums/search.enum.ts b/openmetadata-ui/src/main/resources/ui/src/enums/search.enum.ts index b37e361b8743..8827d555e699 100644 --- a/openmetadata-ui/src/main/resources/ui/src/enums/search.enum.ts +++ b/openmetadata-ui/src/main/resources/ui/src/enums/search.enum.ts @@ -28,6 +28,7 @@ export enum SearchIndex { TAG = 'tag', CONTAINER = 'container', QUERY = 'query', + DATA_CONTRACT = 'dataContract', TEST_CASE = 'testCase', TEST_SUITE = 'testSuite', DATABASE_SCHEMA = 'databaseSchema', diff --git a/openmetadata-ui/src/main/resources/ui/src/interface/search.interface.ts b/openmetadata-ui/src/main/resources/ui/src/interface/search.interface.ts index 7abb32a31cae..d7785b50f162 100644 --- a/openmetadata-ui/src/main/resources/ui/src/interface/search.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/interface/search.interface.ts @@ -31,6 +31,7 @@ import { DatabaseServiceType, } from '../generated/entity/data/database'; import { DatabaseSchema } from '../generated/entity/data/databaseSchema'; +import { DataContract } from '../generated/entity/data/dataContract'; import { Directory } from '../generated/entity/data/directory'; import { File } from '../generated/entity/data/file'; import { Glossary } from '../generated/entity/data/glossary'; @@ -188,6 +189,9 @@ export interface GlossaryTermSearchSource GlossaryTerm {} // extends EntityInterface export interface QuerySearchSource extends SearchSourceBase, Query {} // extends EntityInterface +export interface DataContractSearchSource + extends SearchSourceBase, + DataContract {} // extends EntityInterface export interface TestCaseSearchSource extends SearchSourceBase, Exclude { @@ -333,6 +337,7 @@ export type SearchIndexSearchSourceMapping = { [SearchIndex.TAG]: TagClassSearchSource; [SearchIndex.CONTAINER]: ContainerSearchSource; [SearchIndex.QUERY]: QuerySearchSource; + [SearchIndex.DATA_CONTRACT]: DataContractSearchSource; [SearchIndex.TEST_CASE]: TestCaseSearchSource; [SearchIndex.DATABASE_SCHEMA]: DataBaseSchemaSearchSource; [SearchIndex.DATABASE]: DatabaseSearchSource; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.test.ts index de746fb25360..ccee5f737b0b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.test.ts @@ -86,6 +86,19 @@ describe('getEntitySpecificQueryBuilderFields', () => { advancedSearchClassBase = new AdvancedSearchClassBase(); }); + it('should return data contract specific fields', () => { + const result = advancedSearchClassBase.getEntitySpecificQueryBuilderFields([ + SearchIndex.DATA_CONTRACT, + ]); + + expect(Object.keys(result)).toEqual([ + EntityFields.DATA_CONTRACT_ENTITY_TYPE, + EntityFields.DATA_CONTRACT_ENTITY_FQN, + EntityFields.DATA_CONTRACT_LATEST_RESULT_STATUS, + EntityFields.REVIEWERS, + ]); + }); + it('should return table specific fields', () => { const result = advancedSearchClassBase.getEntitySpecificQueryBuilderFields([ SearchIndex.TABLE, @@ -1307,3 +1320,64 @@ describe('tag-like field autocomplete casing (#31999)', () => { ); }); }); + +describe('getQueryBuilderFields for data contracts', () => { + const advancedSearchClassBase = new AdvancedSearchClassBase(); + const fieldsUnpopulatedOnContracts = [ + EntityFields.SERVICE, + EntityFields.DOMAINS, + EntityFields.DATA_PRODUCT, + EntityFields.TAG, + EntityFields.GLOSSARY_TERMS, + EntityFields.TIER, + EntityFields.CERTIFICATION, + ]; + + it('should offer the contract own fields and none it never populates', () => { + const fields = Object.keys( + advancedSearchClassBase.getQueryBuilderFields({ + entitySearchIndex: [SearchIndex.DATA_CONTRACT], + }) + ); + + expect(fields).toEqual( + expect.arrayContaining([ + EntityFields.OWNERS, + EntityFields.NAME_KEYWORD, + EntityFields.DATA_CONTRACT_ENTITY_FQN, + EntityFields.DATA_CONTRACT_LATEST_RESULT_STATUS, + EntityFields.REVIEWERS, + ]) + ); + + fieldsUnpopulatedOnContracts.forEach((field) => + expect(fields).not.toContain(field) + ); + }); + + it('should drop those fields when contracts are selected alongside tables', () => { + const fields = Object.keys( + advancedSearchClassBase.getQueryBuilderFields({ + entitySearchIndex: [SearchIndex.TABLE, SearchIndex.DATA_CONTRACT], + }) + ); + + fieldsUnpopulatedOnContracts.forEach((field) => + expect(fields).not.toContain(field) + ); + + expect(fields).toContain(EntityFields.OWNERS); + }); + + it('should keep domains and tags for tables alone', () => { + const fields = Object.keys( + advancedSearchClassBase.getQueryBuilderFields({ + entitySearchIndex: [SearchIndex.TABLE], + }) + ); + + expect(fields).toEqual( + expect.arrayContaining([EntityFields.DOMAINS, EntityFields.TAG]) + ); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts index 6849d7981f29..bb8263fca358 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AdvancedSearchClassBase.ts @@ -21,7 +21,7 @@ import { type ListValues, type SelectFieldSettings, } from '@react-awesome-query-builder/antd'; -import { debounce, isEmpty, sortBy, toLower } from 'lodash'; +import { debounce, isEmpty, omit, sortBy, toLower } from 'lodash'; import { SearchOutputType, type CustomPropertyEnumConfig, @@ -44,6 +44,7 @@ import { import { SearchIndex } from '../enums/search.enum'; import type { Config } from '../generated/api/data/createCustomProperty'; import { EntityStatus } from '../generated/entity/data/searchIndex'; +import { ContractExecutionStatus } from '../generated/type/contractExecutionStatus'; import type { CustomPropertySummary } from '../rest/metadataTypeAPI.interface'; import { getAggregateFieldOptions } from '../rest/miscAPI'; import { renderAdvanceSearchButtons } from './AdvancedSearchUtils'; @@ -477,6 +478,79 @@ class AdvancedSearchClassBase { }, }; + // Common fields a search index never populates. A condition on one of them matches nothing in that index, so it is + // not offered while the index is selected — the same rule that keeps entity-specific fields to the common ones. + unsupportedCommonFields: Partial> = { + // A contract has no tags, tier, domains, data products, certification or service of its own. + [SearchIndex.DATA_CONTRACT]: [ + EntityFields.SERVICE, + EntityFields.SERVICE_TYPE, + EntityFields.DOMAINS, + EntityFields.DATA_PRODUCT, + EntityFields.TAG, + EntityFields.TAGS_LABEL_TYPE, + EntityFields.GLOSSARY_TERMS, + EntityFields.CERTIFICATION, + EntityFields.TIER, + EntityFields.TIER_LABEL_TYPE, + ], + }; + + // Fields specific to data contracts + dataContractQueryBuilderFields: Fields = { + [EntityFields.DATA_CONTRACT_ENTITY_TYPE]: { + label: t('label.asset-type'), + type: 'select', + mainWidgetProps: this.mainWidgetProps, + fieldSettings: { + asyncFetch: this.autocomplete({ + searchIndex: SearchIndex.DATA_CONTRACT, + entityField: EntityFields.DATA_CONTRACT_ENTITY_TYPE, + }), + useAsyncSearch: true, + }, + }, + [EntityFields.DATA_CONTRACT_ENTITY_FQN]: { + label: t('label.asset'), + type: 'select', + mainWidgetProps: this.mainWidgetProps, + fieldSettings: { + asyncFetch: this.autocomplete({ + searchIndex: SearchIndex.DATA_CONTRACT, + entityField: EntityFields.DATA_CONTRACT_ENTITY_FQN, + }), + useAsyncSearch: true, + }, + }, + [EntityFields.DATA_CONTRACT_LATEST_RESULT_STATUS]: { + label: t('label.contract-execution-status'), + type: 'select', + operators: LIST_VALUE_OPERATORS, + mainWidgetProps: this.mainWidgetProps, + valueSources: ['value'], + fieldSettings: { + listValues: Object.values(ContractExecutionStatus).map((status) => ({ + value: status, + title: status, + })), + showSearch: true, + useAsyncSearch: false, + }, + }, + [EntityFields.REVIEWERS]: { + label: t('label.reviewer-plural'), + type: 'select', + mainWidgetProps: this.mainWidgetProps, + fieldSettings: { + asyncFetch: this.autocomplete({ + searchIndex: [SearchIndex.USER, SearchIndex.TEAM], + entityField: EntityFields.DISPLAY_NAME_KEYWORD, + }), + useAsyncSearch: true, + }, + }, + }; + /** * Fields specific to dashboard */ @@ -1193,6 +1267,7 @@ class AdvancedSearchClassBase { [SearchIndex.FILE]: this.fileSearchQueryBuilderFields, [SearchIndex.SPREADSHEET]: this.spreadsheetSearchQueryBuilderFields, [SearchIndex.WORKSHEET]: this.worksheetSearchQueryBuilderFields, + [SearchIndex.DATA_CONTRACT]: this.dataContractQueryBuilderFields, [SearchIndex.ALL]: { ...this.tableQueryBuilderFields, ...this.pipelineQueryBuilderFields, @@ -1278,13 +1353,19 @@ class AdvancedSearchClassBase { }, }; - const fieldsConfig = { - ...this.getCommonConfig({ entitySearchIndex }), - ...(shouldAddServiceField ? serviceQueryBuilderFields : {}), - ...this.getEntitySpecificQueryBuilderFields(entitySearchIndex), - ...this.getColumnConfig(entitySearchIndex), - ...this.getColumnTagConfig(entitySearchIndex), - }; + const unsupportedFields = entitySearchIndex.flatMap( + (index) => this.unsupportedCommonFields[index] ?? [] + ); + const fieldsConfig = omit( + { + ...this.getCommonConfig({ entitySearchIndex }), + ...(shouldAddServiceField ? serviceQueryBuilderFields : {}), + ...this.getEntitySpecificQueryBuilderFields(entitySearchIndex), + ...this.getColumnConfig(entitySearchIndex), + ...this.getColumnTagConfig(entitySearchIndex), + }, + unsupportedFields + ); // Sort the fields according to the label const sortedFieldsConfig = sortBy(Object.entries(fieldsConfig), '1.label'); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SearchClassBase.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/SearchClassBase.test.ts index 527fa587008e..c10ddf290372 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SearchClassBase.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SearchClassBase.test.ts @@ -57,6 +57,19 @@ describe('SearchClassBase', () => { searchClassBase = new SearchClassBase(); }); + it('should map data contracts to their own search index both ways', () => { + expect( + searchClassBase.getEntityTypeSearchIndexMapping()[ + EntityType.DATA_CONTRACT + ] + ).toBe(SearchIndex.DATA_CONTRACT); + expect( + searchClassBase.getSearchIndexEntityTypeMapping()[ + SearchIndex.DATA_CONTRACT + ] + ).toBe(EntityType.DATA_CONTRACT); + }); + it('should return the correct search index for each entity type', () => { const searchIndexMapping = searchClassBase.getEntityTypeSearchIndexMapping(); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SearchClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/SearchClassBase.ts index 6db8c7e6e270..68f59da7304a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SearchClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SearchClassBase.ts @@ -110,6 +110,7 @@ class SearchClassBase { [EntityType.TEST_SUITE]: SearchIndex.TEST_SUITE, [EntityType.GLOSSARY]: SearchIndex.GLOSSARY, [EntityType.INGESTION_PIPELINE]: SearchIndex.INGESTION_PIPELINE, + [EntityType.DATA_CONTRACT]: SearchIndex.DATA_CONTRACT, [EntityType.API_SERVICE]: SearchIndex.API_SERVICE, [EntityType.API_COLLECTION]: SearchIndex.API_COLLECTION, [EntityType.API_ENDPOINT]: SearchIndex.API_ENDPOINT, @@ -157,6 +158,7 @@ class SearchClassBase { [SearchIndex.TEST_SUITE]: EntityType.TEST_SUITE, [SearchIndex.GLOSSARY]: EntityType.GLOSSARY, [SearchIndex.INGESTION_PIPELINE]: EntityType.INGESTION_PIPELINE, + [SearchIndex.DATA_CONTRACT]: EntityType.DATA_CONTRACT, [SearchIndex.API_SERVICE]: EntityType.API_SERVICE, [SearchIndex.API_COLLECTION]: EntityType.API_COLLECTION, [SearchIndex.API_ENDPOINT]: EntityType.API_ENDPOINT,