From b18863c4aabcf69e5751ad37e78532e30bae3477 Mon Sep 17 00:00:00 2001 From: Dave Connors Date: Thu, 7 Sep 2023 08:49:22 -0500 Subject: [PATCH 1/9] add semantic model selection method, properly yield semantic models in list --- core/dbt/compilation.py | 4 ++-- core/dbt/graph/selector_methods.py | 38 ++++++++++++++++++++++++++++++ core/dbt/task/list.py | 9 ++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/core/dbt/compilation.py b/core/dbt/compilation.py index 959ad2516d1..aa77ab44bd9 100644 --- a/core/dbt/compilation.py +++ b/core/dbt/compilation.py @@ -183,10 +183,10 @@ def link_node(self, node: GraphMemberNode, manifest: Manifest): def link_graph(self, manifest: Manifest): for source in manifest.sources.values(): self.add_node(source.unique_id) - for semantic_model in manifest.semantic_models.values(): - self.add_node(semantic_model.unique_id) for node in manifest.nodes.values(): self.link_node(node, manifest) + for semantic_model in manifest.semantic_models.values(): + self.link_node(semantic_model, manifest) for exposure in manifest.exposures.values(): self.link_node(exposure, manifest) for metric in manifest.metrics.values(): diff --git a/core/dbt/graph/selector_methods.py b/core/dbt/graph/selector_methods.py index e2a5a1f3f61..feba8a10b1b 100644 --- a/core/dbt/graph/selector_methods.py +++ b/core/dbt/graph/selector_methods.py @@ -18,6 +18,7 @@ ResultNode, ManifestNode, ModelNode, + SemanticModel, ) from dbt.contracts.graph.unparsed import UnparsedVersion from dbt.contracts.state import PreviousState @@ -53,6 +54,7 @@ class MethodName(StrEnum): SourceStatus = "source_status" Wildcard = "wildcard" Version = "version" + SemanticModel = "semantic_model" def is_selected_node(fqn: List[str], node_selector: str, is_versioned: bool) -> bool: @@ -144,6 +146,16 @@ def metric_nodes(self, included_nodes: Set[UniqueId]) -> Iterator[Tuple[UniqueId continue yield unique_id, metric + def semamntic_model_nodes( + self, included_nodes: Set[UniqueId] + ) -> Iterator[Tuple[UniqueId, SemanticModel]]: + + for key, semantic_model in self.manifest.semantic_models.items(): + unique_id = UniqueId(key) + if unique_id not in included_nodes: + continue + yield unique_id, semantic_model + def all_nodes( self, included_nodes: Set[UniqueId] ) -> Iterator[Tuple[UniqueId, SelectorTarget]]: @@ -322,6 +334,31 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu yield node +class SemanticModelSelectorMethod(SelectorMethod): + def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]: + parts = selector.split(".") + target_package = SELECTOR_GLOB + if len(parts) == 1: + target_name = parts[0] + elif len(parts) == 2: + target_package, target_name = parts + else: + msg = ( + 'Invalid semantic model selector value "{}". Semantic models must be of ' + "the form ${{semantic_model_name}} or " + "${{semantic_model_package.semantic_model_name}}" + ).format(selector) + raise DbtRuntimeError(msg) + + for node, real_node in self.semamntic_model_nodes(included_nodes): + if not fnmatch(real_node.package_name, target_package): + continue + if not fnmatch(real_node.name, target_name): + continue + + yield node + + class PathSelectorMethod(SelectorMethod): def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]: """Yields nodes from included that match the given path.""" @@ -761,6 +798,7 @@ class MethodManager: MethodName.Result: ResultSelectorMethod, MethodName.SourceStatus: SourceStatusSelectorMethod, MethodName.Version: VersionSelectorMethod, + MethodName.SemanticModel: SemanticModelSelectorMethod, } def __init__( diff --git a/core/dbt/task/list.py b/core/dbt/task/list.py index 94cd4997cc2..83c462b0210 100644 --- a/core/dbt/task/list.py +++ b/core/dbt/task/list.py @@ -1,6 +1,6 @@ import json -from dbt.contracts.graph.nodes import Exposure, SourceDefinition, Metric +from dbt.contracts.graph.nodes import Exposure, SourceDefinition, Metric, SemanticModel from dbt.flags import get_flags from dbt.graph import ResourceTypeSelector from dbt.task.runnable import GraphRunnableTask @@ -28,6 +28,7 @@ class ListTask(GraphRunnableTask): NodeType.Source, NodeType.Exposure, NodeType.Metric, + NodeType.SemanticModel, ) ) ALL_RESOURCE_VALUES = DEFAULT_RESOURCE_VALUES | frozenset((NodeType.Analysis,)) @@ -74,6 +75,8 @@ def _iterate_selected_nodes(self): yield self.manifest.exposures[node] elif node in self.manifest.metrics: yield self.manifest.metrics[node] + elif node in self.manifest.semantic_models: + yield self.manifest.semantic_models[node] else: raise DbtRuntimeError( f'Got an unexpected result from node selection: "{node}"' @@ -97,6 +100,10 @@ def generate_selectors(self): # metrics are searched for by pkg.metric_name metric_selector = ".".join([node.package_name, node.name]) yield f"metric:{metric_selector}" + elif node.resource_type == NodeType.SemanticModel: + assert isinstance(node, SemanticModel) + semantic_model_selector = ".".join([node.package_name, node.name]) + yield f"semantic_model:{semantic_model_selector}" else: # everything else is from `fqn` yield ".".join(node.fqn) From 493cbd389439cafe29fce4d6b7026a5a6d807b35 Mon Sep 17 00:00:00 2001 From: Dave Connors Date: Thu, 7 Sep 2023 09:12:07 -0500 Subject: [PATCH 2/9] add selector test for semantic models --- tests/unit/test_graph_selector_methods.py | 50 ++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_graph_selector_methods.py b/tests/unit/test_graph_selector_methods.py index 4345f762b55..94153159640 100644 --- a/tests/unit/test_graph_selector_methods.py +++ b/tests/unit/test_graph_selector_methods.py @@ -17,7 +17,9 @@ MetricTypeParams, MetricInputMeasure, Group, + NodeRelation, SeedNode, + SemanticModel, SingularTestNode, GenericTestNode, SourceDefinition, @@ -47,6 +49,7 @@ ExposureSelectorMethod, MetricSelectorMethod, VersionSelectorMethod, + SemanticModelSelectorMethod, ) import dbt.exceptions import dbt.contracts.graph.nodes @@ -427,6 +430,33 @@ def make_group(pkg, name, path=None): ) +def make_semantic_model(pkg: str, name: str, path=None, model=None): + if path is None: + path = "schema.yml" + + if model is None: + model = name + + node_relation = NodeRelation( + alias=model, + schema_name="dbt", + ) + + return SemanticModel( + name=name, + resource_type=NodeType.SemanticModel, + model=model, + node_relation=node_relation, + package_name=pkg, + path=path, + description="Customer entity", + primary_entity="customer", + unique_id=f"semantic_model.{pkg}.{name}", + original_file_path=path, + fqn=[pkg, "semantic_models", name], + ) + + @pytest.fixture def macro_test_unique(): return make_macro( @@ -798,6 +828,7 @@ def manifest( nodes={n.unique_id: n for n in nodes}, sources={s.unique_id: s for s in sources}, macros={m.unique_id: m for m in macros}, + semantic_models={}, docs={}, files={}, exposures={}, @@ -815,7 +846,8 @@ def search_manifest_using_method(manifest, method, selection): set(manifest.nodes) | set(manifest.sources) | set(manifest.exposures) - | set(manifest.metrics), + | set(manifest.metrics) + | set(manifest.semantic_models), selection, ) results = {manifest.expect(uid).search_name for uid in selected} @@ -1247,6 +1279,22 @@ def test_select_metric(manifest): assert search_manifest_using_method(manifest, method, "*_metric") == {"my_metric"} +def test_select_semantic_model(manifest): + semantic_model = make_semantic_model( + "pkg", + "customer", + model="customers", + path="_semantic_models.yml", + ) + manifest.semantic_models[semantic_model.unique_id] = semantic_model + methods = MethodManager(manifest, None) + method = methods.get_method("semantic_model", []) + assert isinstance(method, SemanticModelSelectorMethod) + assert search_manifest_using_method(manifest, method, "customer") == {"customer"} + assert not search_manifest_using_method(manifest, method, "not_customer") + assert search_manifest_using_method(manifest, method, "*omer") == {"customer"} + + @pytest.fixture def previous_state(manifest): writable = copy.deepcopy(manifest).writable_manifest() From a061453b28fe9ced72ab36caa77a284b3e7322c9 Mon Sep 17 00:00:00 2001 From: Dave Connors Date: Thu, 7 Sep 2023 10:06:02 -0500 Subject: [PATCH 3/9] fix typo --- core/dbt/graph/selector_methods.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/dbt/graph/selector_methods.py b/core/dbt/graph/selector_methods.py index feba8a10b1b..8b8a3b5fa13 100644 --- a/core/dbt/graph/selector_methods.py +++ b/core/dbt/graph/selector_methods.py @@ -146,7 +146,7 @@ def metric_nodes(self, included_nodes: Set[UniqueId]) -> Iterator[Tuple[UniqueId continue yield unique_id, metric - def semamntic_model_nodes( + def semantic_model_nodes( self, included_nodes: Set[UniqueId] ) -> Iterator[Tuple[UniqueId, SemanticModel]]: @@ -164,6 +164,7 @@ def all_nodes( self.source_nodes(included_nodes), self.exposure_nodes(included_nodes), self.metric_nodes(included_nodes), + self.semantic_model_nodes(included_nodes), ) def configurable_nodes( @@ -179,6 +180,7 @@ def non_source_nodes( self.parsed_nodes(included_nodes), self.exposure_nodes(included_nodes), self.metric_nodes(included_nodes), + self.semantic_model_nodes(included_nodes), ) def groupable_nodes( @@ -350,7 +352,7 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu ).format(selector) raise DbtRuntimeError(msg) - for node, real_node in self.semamntic_model_nodes(included_nodes): + for node, real_node in self.semantic_model_nodes(included_nodes): if not fnmatch(real_node.package_name, target_package): continue if not fnmatch(real_node.name, target_name): @@ -468,7 +470,7 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu resource_type = NodeType(selector) except ValueError as exc: raise DbtRuntimeError(f'Invalid resource_type selector "{selector}"') from exc - for node, real_node in self.parsed_nodes(included_nodes): + for node, real_node in self.all_nodes(included_nodes): if real_node.resource_type == resource_type: yield node From 08ae8568d9fbabea5ee8ace9d75b605bbca51754 Mon Sep 17 00:00:00 2001 From: Dave Connors Date: Thu, 7 Sep 2023 10:06:45 -0500 Subject: [PATCH 4/9] add semantic model and metric fixtures --- tests/functional/list/fixtures.py | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/functional/list/fixtures.py b/tests/functional/list/fixtures.py index c35988e2999..d5335cc46c2 100644 --- a/tests/functional/list/fixtures.py +++ b/tests/functional/list/fixtures.py @@ -103,6 +103,30 @@ """ +semantic_models__sm_yml = """ +semantic_models: + - name: my_sm + model: ref('outer) + entities: + - name: my_entity + type: primary + expr: id + measures: + - name: total_outer_count + type: count + +""" + +metrics__m_yml = """ +metrics: + - name: total_outer + type: simple + description: The total count of outer + label: Total Outer + type_params: + measure: total_outer_count +""" + @pytest.fixture(scope="class") def snapshots(): @@ -141,6 +165,16 @@ def analyses(): return {"a.sql": analyses__a_sql} +@pytest.fixture(scope="class") +def semantic_models(): + return {"sm.yml": semantic_models__sm_yml} + + +@pytest.fixture(scope="class") +def metrics(): + return {"m.yml": metrics__m_yml} + + @pytest.fixture(scope="class") def project_files( project_root, @@ -150,6 +184,8 @@ def project_files( macros, seeds, analyses, + semantic_models, + metrics, ): write_project_files(project_root, "snapshots", snapshots) write_project_files(project_root, "tests", tests) @@ -157,3 +193,5 @@ def project_files( write_project_files(project_root, "macros", macros) write_project_files(project_root, "seeds", seeds) write_project_files(project_root, "analyses", analyses) + write_project_files(project_root, "semantic_models", semantic_models) + write_project_files(project_root, "metrics", metrics) From d3e7e9b3831cf2f1a89242018c0ded7a99dad2cc Mon Sep 17 00:00:00 2001 From: Dave Connors Date: Thu, 7 Sep 2023 10:07:31 -0500 Subject: [PATCH 5/9] add semantic model to cli param for resource type --- core/dbt/cli/params.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/dbt/cli/params.py b/core/dbt/cli/params.py index 3c016958fb4..1806a412c17 100644 --- a/core/dbt/cli/params.py +++ b/core/dbt/cli/params.py @@ -360,6 +360,7 @@ type=ChoiceTuple( [ "metric", + "semantic_model", "source", "analysis", "model", From 200d07f1abc21d259a197987764b147b9445ad03 Mon Sep 17 00:00:00 2001 From: Dave Connors Date: Fri, 8 Sep 2023 09:33:44 -0500 Subject: [PATCH 6/9] update tests --- core/dbt/contracts/graph/nodes.py | 50 ++++++++++++++++++++ core/dbt/graph/cli.py | 2 +- core/dbt/graph/selector_methods.py | 2 +- tests/functional/list/fixtures.py | 30 +++++++++--- tests/functional/list/test_list.py | 73 ++++++++++++++++++++++++++++-- 5 files changed, 144 insertions(+), 13 deletions(-) diff --git a/core/dbt/contracts/graph/nodes.py b/core/dbt/contracts/graph/nodes.py index b6e685d985f..f6832599088 100644 --- a/core/dbt/contracts/graph/nodes.py +++ b/core/dbt/contracts/graph/nodes.py @@ -1671,6 +1671,56 @@ def primary_entity_reference(self) -> Optional[EntityReference]: else None ) + def same_model(self, old: "SemanticModel") -> bool: + return self.model == old.same_model + + def same_node_relation(self, old: "SemanticModel") -> bool: + return self.node_relation == old.node_relation + + def same_description(self, old: "SemanticModel") -> bool: + return self.description == old.description + + def same_defaults(self, old: "SemanticModel") -> bool: + return self.defaults == old.defaults + + def same_entities(self, old: "SemanticModel") -> bool: + return self.entities == old.entities + + def same_dimensions(self, old: "SemanticModel") -> bool: + return self.dimensions == old.dimensions + + def same_measures(self, old: "SemanticModel") -> bool: + return self.measures == old.measures + + def same_config(self, old: "SemanticModel") -> bool: + return self.config == old.config + + def same_primary_entity(self, old: "SemanticModel") -> bool: + return self.primary_entity == old.primary_entity + + def same_group(self, old: "SemanticModel") -> bool: + return self.group == old.group + + def same_contents(self, old: Optional["SemanticModel"]) -> bool: + # existing when it didn't before is a change! + # metadata/tags changes are not "changes" + if old is None: + return True + + return ( + self.same_model(old) + and self.same_node_relation(old) + and self.same_description(old) + and self.same_defaults(old) + and self.same_entities(old) + and self.same_dimensions(old) + and self.same_measures(old) + and self.same_config(old) + and self.same_primary_entity(old) + and self.same_group(old) + and True + ) + # ==================================== # Patches diff --git a/core/dbt/graph/cli.py b/core/dbt/graph/cli.py index 2950e88415e..5f8620f6ee6 100644 --- a/core/dbt/graph/cli.py +++ b/core/dbt/graph/cli.py @@ -21,7 +21,7 @@ INTERSECTION_DELIMITER = "," -DEFAULT_INCLUDES: List[str] = ["fqn:*", "source:*", "exposure:*", "metric:*"] +DEFAULT_INCLUDES: List[str] = ["fqn:*", "source:*", "exposure:*", "metric:*", "semantic_model:*"] DEFAULT_EXCLUDES: List[str] = [] diff --git a/core/dbt/graph/selector_methods.py b/core/dbt/graph/selector_methods.py index 8b8a3b5fa13..a282dc5b810 100644 --- a/core/dbt/graph/selector_methods.py +++ b/core/dbt/graph/selector_methods.py @@ -578,7 +578,7 @@ def check_macros_modified(self, node): def check_modified_content( self, old: Optional[SelectorTarget], new: SelectorTarget, adapter_type: str ) -> bool: - if isinstance(new, (SourceDefinition, Exposure, Metric)): + if isinstance(new, (SourceDefinition, Exposure, Metric, SemanticModel)): # these all overwrite `same_contents` different_contents = not new.same_contents(old) # type: ignore else: diff --git a/tests/functional/list/fixtures.py b/tests/functional/list/fixtures.py index d5335cc46c2..ea42e2a004f 100644 --- a/tests/functional/list/fixtures.py +++ b/tests/functional/list/fixtures.py @@ -46,7 +46,16 @@ {{ config(materialized='ephemeral') }} -select 1 as id +select + 1 as id, + {{ dbt.date_trunc('day', dbt.current_timestamp()) }} as created_at + +""" + +models__metric_flow = """ + +select + {{ dbt.date_trunc('day', dbt.current_timestamp()) }} as date_day """ @@ -106,14 +115,22 @@ semantic_models__sm_yml = """ semantic_models: - name: my_sm - model: ref('outer) + model: ref('outer') + defaults: + agg_time_dimension: created_at entities: - name: my_entity type: primary expr: id + dimensions: + - name: created_at + type: time + type_params: + time_granularity: day measures: - name: total_outer_count - type: count + agg: count + expr: 1 """ @@ -146,6 +163,9 @@ def models(): "incremental.sql": models__incremental_sql, "docs.md": models__docs_md, "outer.sql": models__outer_sql, + "metricflow_time_spine.sql": models__metric_flow, + "sm.yml": semantic_models__sm_yml, + "m.yml": metrics__m_yml, "sub": {"inner.sql": models__sub__inner_sql}, } @@ -184,8 +204,6 @@ def project_files( macros, seeds, analyses, - semantic_models, - metrics, ): write_project_files(project_root, "snapshots", snapshots) write_project_files(project_root, "tests", tests) @@ -193,5 +211,3 @@ def project_files( write_project_files(project_root, "macros", macros) write_project_files(project_root, "seeds", seeds) write_project_files(project_root, "analyses", analyses) - write_project_files(project_root, "semantic_models", semantic_models) - write_project_files(project_root, "metrics", metrics) diff --git a/tests/functional/list/test_list.py b/tests/functional/list/test_list.py index 19739054a40..25dcd68f72d 100644 --- a/tests/functional/list/test_list.py +++ b/tests/functional/list/test_list.py @@ -12,6 +12,8 @@ macros, seeds, analyses, + semantic_models, + metrics, project_files, ) @@ -151,13 +153,22 @@ def expect_analyses_output(self): def expect_model_output(self): expectations = { - "name": ("ephemeral", "incremental", "inner", "outer"), - "selector": ("test.ephemeral", "test.incremental", "test.sub.inner", "test.outer"), + "name": ("ephemeral", "incremental", "inner", "metricflow_time_spine", "outer"), + "selector": ( + "test.ephemeral", + "test.incremental", + "test.sub.inner", + "test.metricflow_time_spine", + "test.outer", + ), "json": ( { "name": "ephemeral", "package_name": "test", - "depends_on": {"nodes": [], "macros": []}, + "depends_on": { + "nodes": [], + "macros": ["macro.dbt.current_timestamp", "macro.dbt.date_trunc"], + }, "tags": [], "config": { "enabled": True, @@ -262,6 +273,43 @@ def expect_model_output(self): "alias": "inner", "resource_type": "model", }, + { + "name": "metricflow_time_spine", + "package_name": "test", + "depends_on": { + "nodes": [], + "macros": ["macro.dbt.current_timestamp", "macro.dbt.date_trunc"], + }, + "tags": [], + "config": { + "enabled": True, + "group": None, + "materialized": "view", + "post-hook": [], + "tags": [], + "pre-hook": [], + "quoting": {}, + "column_types": {}, + "persist_docs": {}, + "full_refresh": None, + "unique_key": None, + "on_schema_change": "ignore", + "on_configuration_change": "apply", + "database": None, + "schema": None, + "alias": None, + "meta": {}, + "grants": {}, + "packages": [], + "incremental_strategy": None, + "docs": {"node_color": None, "show": True}, + "contract": {"enforced": False}, + }, + "original_file_path": normalize("models/metricflow_time_spine.sql"), + "unique_id": "model.test.metricflow_time_spine", + "alias": "metricflow_time_spine", + "resource_type": "model", + }, { "name": "outer", "package_name": "test", @@ -304,6 +352,7 @@ def expect_model_output(self): self.dir("models/ephemeral.sql"), self.dir("models/incremental.sql"), self.dir("models/sub/inner.sql"), + self.dir("models/metricflow_time_spine.sql"), self.dir("models/outer.sql"), ), } @@ -534,7 +583,10 @@ def expect_all_output(self): "source:test.my_source.my_table", "test.not_null_outer_id", "test.unique_outer_id", + "test.metricflow_time_spine", "test.t", + "semantic_model:test.my_sm", + "metric:test.total_outer", } # analyses have their type inserted into their fqn like tests expected_all = expected_default | {"test.analysis.a"} @@ -559,11 +611,22 @@ def expect_select(self): results = self.run_dbt_ls(["--resource-type", "test", "--select", "+inner"]) assert set(results) == {"test.not_null_outer_id", "test.unique_outer_id"} + results = self.run_dbt_ls(["--resource-type", "semantic_model"]) + assert set(results) == {"semantic_model:test.my_sm"} + + results = self.run_dbt_ls(["--resource-type", "metric"]) + assert set(results) == {"metric:test.total_outer"} + results = self.run_dbt_ls(["--resource-type", "model", "--select", "outer+"]) assert set(results) == {"test.outer", "test.sub.inner"} results = self.run_dbt_ls(["--resource-type", "model", "--exclude", "inner"]) - assert set(results) == {"test.ephemeral", "test.outer", "test.incremental"} + assert set(results) == { + "test.ephemeral", + "test.outer", + "test.metricflow_time_spine", + "test.incremental", + } results = self.run_dbt_ls(["--select", "config.incremental_strategy:delete+insert"]) assert set(results) == {"test.incremental"} @@ -581,6 +644,7 @@ def expect_resource_type_multiple(self): "test.not_null_outer_id", "test.outer", "test.sub.inner", + "test.metricflow_time_spine", "test.t", "test.unique_outer_id", } @@ -593,6 +657,7 @@ def expect_resource_type_multiple(self): "test.incremental", "test.not_null_outer_id", "test.outer", + "test.metricflow_time_spine", "test.sub.inner", "test.t", } From a1aba077ea3a50c539676ce2a25a00a71a155055 Mon Sep 17 00:00:00 2001 From: Michelle Ark Date: Mon, 25 Sep 2023 23:32:32 +0100 Subject: [PATCH 7/9] include metrics + semantic_models in FQN selection --- core/dbt/graph/selector_methods.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/dbt/graph/selector_methods.py b/core/dbt/graph/selector_methods.py index a282dc5b810..f815ee68416 100644 --- a/core/dbt/graph/selector_methods.py +++ b/core/dbt/graph/selector_methods.py @@ -224,8 +224,8 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu :param str selector: The selector or node name """ - parsed_nodes = list(self.parsed_nodes(included_nodes)) - for node, real_node in parsed_nodes: + non_source_nodes = list(self.non_source_nodes(included_nodes)) + for node, real_node in non_source_nodes: if self.node_is_match(selector, real_node.fqn, real_node.is_versioned): yield node From b2427f3129c68b031a1d8a7ec7c3b26fe88d56a6 Mon Sep 17 00:00:00 2001 From: Michelle Ark Date: Mon, 25 Sep 2023 23:33:12 +0100 Subject: [PATCH 8/9] changelog entry --- .changes/unreleased/Fixes-20230925-233306.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/unreleased/Fixes-20230925-233306.yaml diff --git a/.changes/unreleased/Fixes-20230925-233306.yaml b/.changes/unreleased/Fixes-20230925-233306.yaml new file mode 100644 index 00000000000..1e078932bf7 --- /dev/null +++ b/.changes/unreleased/Fixes-20230925-233306.yaml @@ -0,0 +1,6 @@ +kind: Fixes +body: semantic models in graph selection +time: 2023-09-25T23:33:06.754344+01:00 +custom: + Author: dave-connors-3 michelleark + Issue: "8589" From 89d6aad11f297aa64d39dab9ac9b9ba808fde47a Mon Sep 17 00:00:00 2001 From: Michelle Ark Date: Tue, 26 Sep 2023 00:07:06 +0100 Subject: [PATCH 9/9] access:protected in expected_model json --- tests/functional/list/test_list.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/list/test_list.py b/tests/functional/list/test_list.py index c2bebd7407d..582258802a3 100644 --- a/tests/functional/list/test_list.py +++ b/tests/functional/list/test_list.py @@ -307,6 +307,7 @@ def expect_model_output(self): "incremental_strategy": None, "docs": {"node_color": None, "show": True}, "contract": {"enforced": False}, + "access": "protected", }, "original_file_path": normalize("models/metricflow_time_spine.sql"), "unique_id": "model.test.metricflow_time_spine",