From 00be51f299302cf02c83c3a2f9254d20a447589f Mon Sep 17 00:00:00 2001 From: Suman Maharana Date: Tue, 7 Jan 2025 16:37:30 +0530 Subject: [PATCH] Fixes #17747: dbt update owners (#19144) * Fixes 17747: dbt update owners * update messages * addressed comments * py_format * py_format * Added tests --- .../source/database/dbt/dbt_service.py | 11 +++++ .../ingestion/source/database/dbt/metadata.py | 42 ++++++++++++++++++ ingestion/tests/unit/test_dbt.py | 8 ++++ .../connectors/yaml/dbt/source-config-def.md | 2 + .../v1.6/connectors/yaml/dbt/source-config.md | 1 + .../connectors/yaml/dbt/source-config-def.md | 2 + .../v1.7/connectors/yaml/dbt/source-config.md | 1 + .../workflows/dbt/ingest-dbt-owner.md | 14 ++++-- .../schemas/metadataIngestion/dbtPipeline.md | 1 + .../workflows/dbt/ingest-dbt-owner.md | 14 ++++-- .../schemas/metadataIngestion/dbtPipeline.md | 1 + .../dbt/dbt-features/dbt-update-owners.webp | Bin 0 -> 13144 bytes .../dbt/dbt-features/dbt-update-owners.webp | Bin 0 -> 13144 bytes .../schema/metadataIngestion/dbtPipeline.json | 5 +++ .../locales/en-US/Database/workflows/dbt.md | 10 +++++ .../metadataIngestion/dbtPipeline.ts | 8 ++-- 16 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 openmetadata-docs/images/v1.6/features/ingestion/workflows/dbt/dbt-features/dbt-update-owners.webp create mode 100644 openmetadata-docs/images/v1.7/features/ingestion/workflows/dbt/dbt-features/dbt-update-owners.webp diff --git a/ingestion/src/metadata/ingestion/source/database/dbt/dbt_service.py b/ingestion/src/metadata/ingestion/source/database/dbt/dbt_service.py index 5021f4c5fb2..ec6ea35c97e 100644 --- a/ingestion/src/metadata/ingestion/source/database/dbt/dbt_service.py +++ b/ingestion/src/metadata/ingestion/source/database/dbt/dbt_service.py @@ -122,6 +122,11 @@ class DbtServiceTopology(ServiceTopology): processor="process_dbt_descriptions", nullable=True, ), + NodeStage( + type_=DataModelLink, + processor="process_dbt_owners", + nullable=True, + ), ], ) process_dbt_tests: Annotated[ @@ -293,6 +298,12 @@ class DbtServiceSource(TopologyRunnerMixin, Source, ABC): Method to process DBT descriptions using patch APIs """ + @abstractmethod + def process_dbt_owners(self, data_model_link: DataModelLink): + """ + Method to process DBT owners using patch APIs + """ + def get_dbt_tests(self) -> dict: """ Prepare the DBT tests diff --git a/ingestion/src/metadata/ingestion/source/database/dbt/metadata.py b/ingestion/src/metadata/ingestion/source/database/dbt/metadata.py index 66e53a36fad..668d64eebdb 100644 --- a/ingestion/src/metadata/ingestion/source/database/dbt/metadata.py +++ b/ingestion/src/metadata/ingestion/source/database/dbt/metadata.py @@ -61,6 +61,7 @@ from metadata.ingestion.api.models import Either from metadata.ingestion.lineage.models import ConnectionTypeDialectMapper from metadata.ingestion.lineage.sql_lineage import get_lineage_by_query from metadata.ingestion.models.ometa_classification import OMetaTagAndClassification +from metadata.ingestion.models.patch_request import PatchedEntity, PatchRequest from metadata.ingestion.models.table_metadata import ColumnDescription from metadata.ingestion.ometa.client import APIError from metadata.ingestion.ometa.ometa_api import OpenMetadata @@ -890,6 +891,47 @@ class DbtSource(DbtServiceSource): f"to update dbt description: {exc}" ) + def process_dbt_owners( + self, data_model_link: DataModelLink + ) -> Iterable[Either[PatchedEntity]]: + """ + Method to process DBT owners + """ + table_entity: Table = data_model_link.table_entity + if table_entity: + logger.debug( + f"Processing DBT owners for: {table_entity.fullyQualifiedName.root}" + ) + try: + data_model = data_model_link.datamodel + if ( + data_model.resourceType != DbtCommonEnum.SOURCE.value + and self.source_config.dbtUpdateOwners + ): + logger.debug( + f"Overwriting owners with DBT owners: {table_entity.fullyQualifiedName.root}" + ) + if data_model.owners: + new_entity = deepcopy(table_entity) + new_entity.owners = data_model.owners + yield Either( + right=PatchRequest( + original_entity=table_entity, + new_entity=new_entity, + override_metadata=True, + ) + ) + + except Exception as exc: # pylint: disable=broad-except + yield Either( + left=StackTraceError( + name=str(table_entity.fullyQualifiedName.root), + error=f"Failed to parse the node" + f"{table_entity.fullyQualifiedName.root} to update dbt owner: {exc}", + stackTrace=traceback.format_exc(), + ) + ) + def create_dbt_tests_definition( self, dbt_test: dict ) -> Iterable[Either[CreateTestDefinitionRequest]]: diff --git a/ingestion/tests/unit/test_dbt.py b/ingestion/tests/unit/test_dbt.py index 1c2db6edc0a..ffe24f33728 100644 --- a/ingestion/tests/unit/test_dbt.py +++ b/ingestion/tests/unit/test_dbt.py @@ -53,6 +53,7 @@ mock_dbt_config = { "dbtRunResultsFilePath": "sample/dbt_files/run_results.json", "dbtSourcesFilePath": "sample/dbt_files/sources.json", }, + "dbtUpdateOwners": True, } }, }, @@ -675,6 +676,7 @@ class DbtUnitTest(TestCase): data_model_link.right.table_entity.fullyQualifiedName.root, EXPECTED_DATA_MODEL_FQNS, ) + self.check_process_dbt_owners(data_model_link.right) data_model_list.append(data_model_link.right.datamodel) for _, (expected, original) in enumerate( @@ -682,6 +684,12 @@ class DbtUnitTest(TestCase): ): self.assertEqual(expected, original) + def check_process_dbt_owners(self, data_model_link): + process_dbt_owners = self.dbt_source_obj.process_dbt_owners(data_model_link) + for entity in process_dbt_owners: + entity_owner = entity.right.new_entity.owners + self.assertEqual(entity_owner, MOCK_OWNER) + @patch("metadata.ingestion.ometa.mixins.es_mixin.ESMixin.es_search_from_fqn") def test_upstream_nodes_for_lineage(self, es_search_from_fqn): expected_upstream_nodes = [ diff --git a/openmetadata-docs/content/partials/v1.6/connectors/yaml/dbt/source-config-def.md b/openmetadata-docs/content/partials/v1.6/connectors/yaml/dbt/source-config-def.md index 1938909593e..eb3c11a332e 100644 --- a/openmetadata-docs/content/partials/v1.6/connectors/yaml/dbt/source-config-def.md +++ b/openmetadata-docs/content/partials/v1.6/connectors/yaml/dbt/source-config-def.md @@ -4,6 +4,8 @@ **dbtUpdateDescriptions**: Configuration to update the description from dbt or not. If set to true descriptions from dbt will override the already present descriptions on the entity. For more details visit [here](/connectors/ingestion/workflows/dbt/ingest-dbt-descriptions) +**dbtUpdateOwners**: Configuration to update the owner from dbt or not. If set to true owners from dbt will override the already present owners on the entity. For more details visit [here](/connectors/ingestion/workflows/dbt/ingest-dbt-owner) + **includeTags**: true or false, to ingest tags from dbt. Default is true. **dbtClassificationName**: Custom OpenMetadata Classification name for dbt tags. diff --git a/openmetadata-docs/content/partials/v1.6/connectors/yaml/dbt/source-config.md b/openmetadata-docs/content/partials/v1.6/connectors/yaml/dbt/source-config.md index a697981792a..8f7662283dc 100644 --- a/openmetadata-docs/content/partials/v1.6/connectors/yaml/dbt/source-config.md +++ b/openmetadata-docs/content/partials/v1.6/connectors/yaml/dbt/source-config.md @@ -1,5 +1,6 @@ ```yaml {% srNumber=120 %} # dbtUpdateDescriptions: true or false + # dbtUpdateOwners: true or false # includeTags: true or false # dbtClassificationName: dbtTags # databaseFilterPattern: diff --git a/openmetadata-docs/content/partials/v1.7/connectors/yaml/dbt/source-config-def.md b/openmetadata-docs/content/partials/v1.7/connectors/yaml/dbt/source-config-def.md index 1938909593e..eb3c11a332e 100644 --- a/openmetadata-docs/content/partials/v1.7/connectors/yaml/dbt/source-config-def.md +++ b/openmetadata-docs/content/partials/v1.7/connectors/yaml/dbt/source-config-def.md @@ -4,6 +4,8 @@ **dbtUpdateDescriptions**: Configuration to update the description from dbt or not. If set to true descriptions from dbt will override the already present descriptions on the entity. For more details visit [here](/connectors/ingestion/workflows/dbt/ingest-dbt-descriptions) +**dbtUpdateOwners**: Configuration to update the owner from dbt or not. If set to true owners from dbt will override the already present owners on the entity. For more details visit [here](/connectors/ingestion/workflows/dbt/ingest-dbt-owner) + **includeTags**: true or false, to ingest tags from dbt. Default is true. **dbtClassificationName**: Custom OpenMetadata Classification name for dbt tags. diff --git a/openmetadata-docs/content/partials/v1.7/connectors/yaml/dbt/source-config.md b/openmetadata-docs/content/partials/v1.7/connectors/yaml/dbt/source-config.md index a697981792a..8f7662283dc 100644 --- a/openmetadata-docs/content/partials/v1.7/connectors/yaml/dbt/source-config.md +++ b/openmetadata-docs/content/partials/v1.7/connectors/yaml/dbt/source-config.md @@ -1,5 +1,6 @@ ```yaml {% srNumber=120 %} # dbtUpdateDescriptions: true or false + # dbtUpdateOwners: true or false # includeTags: true or false # dbtClassificationName: dbtTags # databaseFilterPattern: diff --git a/openmetadata-docs/content/v1.6.x/connectors/ingestion/workflows/dbt/ingest-dbt-owner.md b/openmetadata-docs/content/v1.6.x/connectors/ingestion/workflows/dbt/ingest-dbt-owner.md index b85174d24ad..0fbdbc7a7de 100644 --- a/openmetadata-docs/content/v1.6.x/connectors/ingestion/workflows/dbt/ingest-dbt-owner.md +++ b/openmetadata-docs/content/v1.6.x/connectors/ingestion/workflows/dbt/ingest-dbt-owner.md @@ -138,8 +138,16 @@ After running the ingestion workflow with dbt you can see the created user or te -{% note %} +## Overriding the existing table Owners -If a table already has a owner linked to it, owner from the dbt will not update the current owner. +To establish a unified and reliable system for owners, a single source of truth is necessary. It either is directly OpenMetadata, if individuals want to go there and keep updating, or if they prefer to keep it centralized in dbt, then we can always rely on that directly. -{% /note %} +When the `Update Owners` toggle is enabled during the configuration of dbt ingestion, existing owners of tables will be overwritten with the dbt owners. + +If toggle is disabled during the configuration of dbt ingestion, dbt owners will only be updated for tables in OpenMetadata that currently have no owners. Existing owners will remain unchanged and will not be overwritten with dbt owners. + +{% image + src="/images/v1.6/features/ingestion/workflows/dbt/dbt-features/dbt-update-owners.webp" + alt="update-dbt-owners" + caption="Update dbt Owners" + /%} diff --git a/openmetadata-docs/content/v1.6.x/main-concepts/metadata-standard/schemas/metadataIngestion/dbtPipeline.md b/openmetadata-docs/content/v1.6.x/main-concepts/metadata-standard/schemas/metadataIngestion/dbtPipeline.md index 842dfc10267..fff8d454a04 100644 --- a/openmetadata-docs/content/v1.6.x/main-concepts/metadata-standard/schemas/metadataIngestion/dbtPipeline.md +++ b/openmetadata-docs/content/v1.6.x/main-concepts/metadata-standard/schemas/metadataIngestion/dbtPipeline.md @@ -12,6 +12,7 @@ slug: /main-concepts/metadata-standard/schemas/metadataingestion/dbtpipeline - **`type`**: Pipeline type. Refer to *#/definitions/dbtConfigType*. Default: `DBT`. - **`dbtConfigSource`**: Available sources to fetch DBT catalog and manifest files. - **`dbtUpdateDescriptions`** *(boolean)*: Optional configuration to update the description from DBT or not. Default: `False`. +- **`dbtUpdateOwners`** *(boolean)*: Optional configuration to update the owners from DBT or not. Default: `False`. - **`includeTags`** *(boolean)*: Optional configuration to toggle the tags ingestion. Default: `True`. - **`dbtClassificationName`** *(string)*: Custom OpenMetadata Classification name for dbt tags. Default: `dbtTags`. - **`schemaFilterPattern`**: Regex to only fetch tables or databases that matches the pattern. Refer to *../type/filterPattern.json#/definitions/filterPattern*. diff --git a/openmetadata-docs/content/v1.7.x-SNAPSHOT/connectors/ingestion/workflows/dbt/ingest-dbt-owner.md b/openmetadata-docs/content/v1.7.x-SNAPSHOT/connectors/ingestion/workflows/dbt/ingest-dbt-owner.md index 00d16038a62..22fbef5d3c2 100644 --- a/openmetadata-docs/content/v1.7.x-SNAPSHOT/connectors/ingestion/workflows/dbt/ingest-dbt-owner.md +++ b/openmetadata-docs/content/v1.7.x-SNAPSHOT/connectors/ingestion/workflows/dbt/ingest-dbt-owner.md @@ -138,8 +138,16 @@ After running the ingestion workflow with dbt you can see the created user or te -{% note %} +## Overriding the existing table Owners -If a table already has a owner linked to it, owner from the dbt will not update the current owner. +To establish a unified and reliable system for owners, a single source of truth is necessary. It either is directly OpenMetadata, if individuals want to go there and keep updating, or if they prefer to keep it centralized in dbt, then we can always rely on that directly. -{% /note %} +When the `Update Owners` toggle is enabled during the configuration of dbt ingestion, existing owners of tables will be overwritten with the dbt owners. + +If toggle is disabled during the configuration of dbt ingestion, dbt owners will only be updated for tables in OpenMetadata that currently have no owners. Existing owners will remain unchanged and will not be overwritten with dbt owners. + +{% image + src="/images/v1.6/features/ingestion/workflows/dbt/dbt-features/dbt-update-owners.webp" + alt="update-dbt-owners" + caption="Update dbt Owners" + /%} diff --git a/openmetadata-docs/content/v1.7.x-SNAPSHOT/main-concepts/metadata-standard/schemas/metadataIngestion/dbtPipeline.md b/openmetadata-docs/content/v1.7.x-SNAPSHOT/main-concepts/metadata-standard/schemas/metadataIngestion/dbtPipeline.md index 842dfc10267..0b27c51b5dd 100644 --- a/openmetadata-docs/content/v1.7.x-SNAPSHOT/main-concepts/metadata-standard/schemas/metadataIngestion/dbtPipeline.md +++ b/openmetadata-docs/content/v1.7.x-SNAPSHOT/main-concepts/metadata-standard/schemas/metadataIngestion/dbtPipeline.md @@ -12,6 +12,7 @@ slug: /main-concepts/metadata-standard/schemas/metadataingestion/dbtpipeline - **`type`**: Pipeline type. Refer to *#/definitions/dbtConfigType*. Default: `DBT`. - **`dbtConfigSource`**: Available sources to fetch DBT catalog and manifest files. - **`dbtUpdateDescriptions`** *(boolean)*: Optional configuration to update the description from DBT or not. Default: `False`. +- **`dbtUpdateOwners`** *(boolean)*: Optional configuration to update the owner from DBT or not. Default: `False`. - **`includeTags`** *(boolean)*: Optional configuration to toggle the tags ingestion. Default: `True`. - **`dbtClassificationName`** *(string)*: Custom OpenMetadata Classification name for dbt tags. Default: `dbtTags`. - **`schemaFilterPattern`**: Regex to only fetch tables or databases that matches the pattern. Refer to *../type/filterPattern.json#/definitions/filterPattern*. diff --git a/openmetadata-docs/images/v1.6/features/ingestion/workflows/dbt/dbt-features/dbt-update-owners.webp b/openmetadata-docs/images/v1.6/features/ingestion/workflows/dbt/dbt-features/dbt-update-owners.webp new file mode 100644 index 0000000000000000000000000000000000000000..b9e0bd91c42b631caaf1a61f23a968bb42d1cfc8 GIT binary patch literal 13144 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 O0sq@yO3Gox!2bty=c