diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e596be3ba8..3172616d4b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Increased the supported relation name length in postgres from 29 to 51 ([#2850](https://github.com/fishtown-analytics/dbt/pull/2850)) - dbt list command always return 0 as exit code ([#2886](https://github.com/fishtown-analytics/dbt/issues/2886), [#2892](https://github.com/fishtown-analytics/dbt/issues/2892)) - Set default `materialized` for test node configs to `test` ([#2806](https://github.com/fishtown-analytics/dbt/issues/2806), [#2902](https://github.com/fishtown-analytics/dbt/pull/2902)) +- Allow docs blocks in exposure descriptions ([#2913](https://github.com/fishtown-analytics/dbt/issues/2913), [#2920](https://github.com/fishtown-analytics/dbt/pull/2920)) - Use original file path instead of absolute path as checksum for big seeds ([#2927](https://github.com/fishtown-analytics/dbt/issues/2927), [#2939](https://github.com/fishtown-analytics/dbt/pull/2939)) ### Under the hood @@ -33,6 +34,7 @@ Contributors: - [@franloza](https://github.com/franloza) ([#2837](https://github.com/fishtown-analytics/dbt/pull/2837)) - [@max-sixty](https://github.com/max-sixty) ([#2877](https://github.com/fishtown-analytics/dbt/pull/2877), [#2908](https://github.com/fishtown-analytics/dbt/pull/2908)) - [@rsella](https://github.com/rsella) ([#2892](https://github.com/fishtown-analytics/dbt/issues/2892)) +- [@joellabes](https://github.com/joellabes) ([#2913](https://github.com/fishtown-analytics/dbt/issues/2913)) - [@plotneishestvo](https://github.com/plotneishestvo) ([#2896](https://github.com/fishtown-analytics/dbt/issues/2896)) - [@db-magnus](https://github.com/db-magnus) ([#2892](https://github.com/fishtown-analytics/dbt/issues/2892)) diff --git a/core/dbt/contracts/graph/parsed.py b/core/dbt/contracts/graph/parsed.py index 422c78f5390..0a7e41c26a0 100644 --- a/core/dbt/contracts/graph/parsed.py +++ b/core/dbt/contracts/graph/parsed.py @@ -654,9 +654,9 @@ class ParsedExposure(UnparsedBaseNode, HasUniqueID, HasFqn): type: ExposureType owner: ExposureOwner resource_type: NodeType = NodeType.Exposure + description: str = '' maturity: Optional[MaturityType] = None url: Optional[str] = None - description: Optional[str] = None depends_on: DependsOn = field(default_factory=DependsOn) refs: List[List[str]] = field(default_factory=list) sources: List[List[str]] = field(default_factory=list) diff --git a/core/dbt/contracts/graph/unparsed.py b/core/dbt/contracts/graph/unparsed.py index 57da34f4a24..6e272bafaf7 100644 --- a/core/dbt/contracts/graph/unparsed.py +++ b/core/dbt/contracts/graph/unparsed.py @@ -411,11 +411,11 @@ class ExposureOwner(JsonSchemaMixin, Replaceable): @dataclass -class UnparsedExposure(JsonSchemaMixin, Replaceable): +class UnparsedExposure(HasYamlMetadata, Replaceable): name: str type: ExposureType owner: ExposureOwner + description: str = '' maturity: Optional[MaturityType] = None url: Optional[str] = None - description: Optional[str] = None depends_on: List[str] = field(default_factory=list) diff --git a/core/dbt/node_types.py b/core/dbt/node_types.py index 8ba6d5900ea..e6502ca7030 100644 --- a/core/dbt/node_types.py +++ b/core/dbt/node_types.py @@ -46,6 +46,7 @@ def documentable(cls) -> List['NodeType']: cls.Source, cls.Macro, cls.Analysis, + cls.Exposure ] def pluralize(self) -> str: diff --git a/core/dbt/parser/manifest.py b/core/dbt/parser/manifest.py index 9af7f7d6635..5b9928786a4 100644 --- a/core/dbt/parser/manifest.py +++ b/core/dbt/parser/manifest.py @@ -622,6 +622,12 @@ def _process_docs_for_macro( arg.description = get_rendered(arg.description, context) +def _process_docs_for_exposure( + context: Dict[str, Any], exposure: ParsedExposure +) -> None: + exposure.description = get_rendered(exposure.description, context) + + def process_docs(manifest: Manifest, config: RuntimeConfig): for node in manifest.nodes.values(): ctx = generate_runtime_docs( @@ -647,6 +653,14 @@ def process_docs(manifest: Manifest, config: RuntimeConfig): config.project_name, ) _process_docs_for_macro(ctx, macro) + for exposure in manifest.exposures.values(): + ctx = generate_runtime_docs( + config, + exposure, + manifest, + config.project_name, + ) + _process_docs_for_exposure(ctx, exposure) def _process_refs_for_exposure( diff --git a/test/integration/029_docs_generate_tests/ref_models/docs.md b/test/integration/029_docs_generate_tests/ref_models/docs.md index 1918e825b44..c5ad96862c6 100644 --- a/test/integration/029_docs_generate_tests/ref_models/docs.md +++ b/test/integration/029_docs_generate_tests/ref_models/docs.md @@ -25,3 +25,7 @@ My table {% docs column_info %} An ID field {% enddocs %} + +{% docs notebook_info %} +A description of the complex exposure +{% enddocs %} diff --git a/test/integration/029_docs_generate_tests/ref_models/schema.yml b/test/integration/029_docs_generate_tests/ref_models/schema.yml index 3cc4c7aac10..99ac66900f6 100644 --- a/test/integration/029_docs_generate_tests/ref_models/schema.yml +++ b/test/integration/029_docs_generate_tests/ref_models/schema.yml @@ -29,3 +29,15 @@ sources: columns: - name: id description: "{{ doc('column_info') }}" + +exposures: + - name: notebook_exposure + type: notebook + depends_on: + - ref('view_summary') + owner: + email: something@example.com + name: Some name + description: "{{ doc('notebook_info') }}" + maturity: medium + url: http://example.com/notebook/1 diff --git a/test/integration/029_docs_generate_tests/test_docs_generate.py b/test/integration/029_docs_generate_tests/test_docs_generate.py index 56907b3e259..c597fd097d7 100644 --- a/test/integration/029_docs_generate_tests/test_docs_generate.py +++ b/test/integration/029_docs_generate_tests/test_docs_generate.py @@ -1567,7 +1567,7 @@ def expected_seeded_manifest(self, model_database=None, quote_model=False): 'macros': [], 'nodes': ['model.test.model', 'model.test.second_model'] }, - 'description': 'A description of the complex exposure', + 'description': 'A description of the complex exposure\n', 'fqn': ['test', 'notebook_exposure'], 'maturity': 'medium', 'name': 'notebook_exposure', @@ -1594,7 +1594,7 @@ def expected_seeded_manifest(self, model_database=None, quote_model=False): 'model.test.model' ], }, - 'description': None, + 'description': '', 'fqn': ['test', 'simple_exposure'], 'name': 'simple_exposure', 'original_file_path': self.dir('models/schema.yml'), @@ -1992,7 +1992,32 @@ def expected_postgres_references_manifest(self, model_database=None): 'unrendered_config': {} }, }, - 'exposures': {}, + 'exposures': { + 'exposure.test.notebook_exposure': { + 'depends_on': { + 'macros': [], + 'nodes': ['model.test.view_summary'] + }, + 'description': 'A description of the complex exposure', + 'fqn': ['test', 'notebook_exposure'], + 'maturity': 'medium', + 'name': 'notebook_exposure', + 'original_file_path': self.dir('ref_models/schema.yml'), + 'owner': { + 'email': 'something@example.com', + 'name': 'Some name' + }, + 'package_name': 'test', + 'path': 'schema.yml', + 'refs': [['view_summary']], + 'resource_type': 'exposure', + 'root_path': self.test_root_realpath, + 'sources': [], + 'type': 'notebook', + 'unique_id': 'exposure.test.notebook_exposure', + 'url': 'http://example.com/notebook/1' + }, + }, 'selectors': {}, 'docs': { 'dbt.__overview__': ANY, @@ -2073,6 +2098,15 @@ def expected_postgres_references_manifest(self, model_database=None): 'root_path': self.test_root_realpath, 'unique_id': 'test.macro_info', }, + 'test.notebook_info': { + 'block_contents': 'A description of the complex exposure', + 'name': 'notebook_info', + 'original_file_path': docs_path, + 'package_name': 'test', + 'path': 'docs.md', + 'root_path': self.test_root_realpath, + 'unique_id': 'test.notebook_info' + }, 'test.macro_arg_info': { 'block_contents': 'The model for my custom test', 'name': 'macro_arg_info', @@ -2085,8 +2119,9 @@ def expected_postgres_references_manifest(self, model_database=None): }, 'child_map': { 'model.test.ephemeral_copy': ['model.test.ephemeral_summary'], + 'exposure.test.notebook_exposure': [], 'model.test.ephemeral_summary': ['model.test.view_summary'], - 'model.test.view_summary': [], + 'model.test.view_summary': ['exposure.test.notebook_exposure'], 'seed.test.seed': ['snapshot.test.snapshot_seed'], 'snapshot.test.snapshot_seed': [], 'source.test.my_source.my_table': ['model.test.ephemeral_copy'], @@ -2095,6 +2130,7 @@ def expected_postgres_references_manifest(self, model_database=None): 'model.test.ephemeral_copy': ['source.test.my_source.my_table'], 'model.test.ephemeral_summary': ['model.test.ephemeral_copy'], 'model.test.view_summary': ['model.test.ephemeral_summary'], + 'exposure.test.notebook_exposure': ['model.test.view_summary'], 'seed.test.seed': [], 'snapshot.test.snapshot_seed': ['seed.test.seed'], 'source.test.my_source.my_table': [], diff --git a/test/unit/test_contracts_graph_parsed.py b/test/unit/test_contracts_graph_parsed.py index 7fc6566eafd..dc3d593241e 100644 --- a/test/unit/test_contracts_graph_parsed.py +++ b/test/unit/test_contracts_graph_parsed.py @@ -2017,6 +2017,7 @@ def minimal_parsed_exposure_dict(): 'path': 'models/something.yml', 'root_path': '/usr/src/app', 'original_file_path': 'models/something.yml', + 'description': '' } @@ -2041,6 +2042,7 @@ def basic_parsed_exposure_dict(): 'path': 'models/something.yml', 'root_path': '/usr/src/app', 'original_file_path': 'models/something.yml', + 'description': '' } @@ -2056,6 +2058,7 @@ def basic_parsed_exposure_object(): root_path='/usr/src/app', original_file_path='models/something.yml', owner=ExposureOwner(email='test@example.com'), + description='' ) diff --git a/test/unit/test_contracts_graph_unparsed.py b/test/unit/test_contracts_graph_unparsed.py index 0ca5a749928..32ae398bf3a 100644 --- a/test/unit/test_contracts_graph_unparsed.py +++ b/test/unit/test_contracts_graph_unparsed.py @@ -576,6 +576,7 @@ class TestUnparsedExposure(ContractTestCase): def get_ok_dict(self): return { + 'yaml_key': 'exposures', 'name': 'my_exposure', 'type': 'dashboard', 'owner': { @@ -587,11 +588,14 @@ def get_ok_dict(self): 'depends_on': [ 'ref("my_model")', 'source("raw", "source_table")', - ] + ], + 'original_file_path': '/some/fake/path', + 'package_name': 'test' } def test_ok(self): exposure = self.ContractType( + yaml_key='exposures', name='my_exposure', type=ExposureType.Dashboard, owner=ExposureOwner(email='name@example.com'), @@ -599,6 +603,8 @@ def test_ok(self): url='https://example.com/dashboards/1', description='A exposure', depends_on=['ref("my_model")', 'source("raw", "source_table")'], + original_file_path='/some/fake/path', + package_name='test' ) dct = self.get_ok_dict() self.assert_symmetric(exposure, dct)