Skip to content

Commit

Permalink
Add tags to SavedQueries (#10987)
Browse files Browse the repository at this point in the history
  • Loading branch information
theyostalservice authored Dec 19, 2024
1 parent bf18b59 commit 97ffc37
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 5 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/Features-20241216-095435.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: Features
body: Support "tags" in Saved Queries
time: 2024-12-16T09:54:35.327675-08:00
custom:
Author: theyostalservice
Issue: "11155"
8 changes: 7 additions & 1 deletion core/dbt/artifacts/resources/v1/saved_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@

import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Literal, Optional
from typing import Any, Dict, List, Literal, Optional, Union

from dbt.artifacts.resources.base import GraphResource
from dbt.artifacts.resources.types import NodeType
from dbt.artifacts.resources.v1.components import DependsOn, RefArgs
from dbt.artifacts.resources.v1.config import list_str, metas
from dbt.artifacts.resources.v1.semantic_layer_components import (
SourceFileMetadata,
WhereFilterIntersection,
)
from dbt_common.contracts.config.base import BaseConfig, CompareBehavior, MergeBehavior
from dbt_common.contracts.config.metadata import ShowBehavior
from dbt_common.dataclass_schema import dbtClassMixin
from dbt_semantic_interfaces.type_enums.export_destination_type import (
ExportDestinationType,
Expand Down Expand Up @@ -95,6 +97,10 @@ class SavedQuery(SavedQueryMandatory):
depends_on: DependsOn = field(default_factory=DependsOn)
created_at: float = field(default_factory=lambda: time.time())
refs: List[RefArgs] = field(default_factory=list)
tags: Union[List[str], str] = field(
default_factory=list_str,
metadata=metas(ShowBehavior.Hide, MergeBehavior.Append, CompareBehavior.Exclude),
)

@property
def metrics(self) -> List[str]:
Expand Down
4 changes: 4 additions & 0 deletions core/dbt/contracts/graph/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1647,6 +1647,9 @@ def same_exports(self, old: "SavedQuery") -> bool:

return True

def same_tags(self, old: "SavedQuery") -> bool:
return self.tags == old.tags

def same_contents(self, old: Optional["SavedQuery"]) -> bool:
# existing when it didn't before is a change!
# metadata/tags changes are not "changes"
Expand All @@ -1662,6 +1665,7 @@ def same_contents(self, old: Optional["SavedQuery"]) -> bool:
and self.same_config(old)
and self.same_group(old)
and self.same_exports(old)
and self.same_tags(old)
and True
)

Expand Down
9 changes: 9 additions & 0 deletions core/dbt/contracts/graph/unparsed.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@
UnitTestOutputFixture,
UnitTestOverrides,
)
from dbt.artifacts.resources.v1.config import list_str, metas
from dbt.exceptions import ParsingError
from dbt.node_types import NodeType
from dbt_common.contracts.config.base import CompareBehavior, MergeBehavior
from dbt_common.contracts.config.metadata import ShowBehavior
from dbt_common.contracts.config.properties import AdditionalPropertiesMixin
from dbt_common.contracts.util import Mergeable
from dbt_common.dataclass_schema import (
Expand Down Expand Up @@ -740,6 +743,12 @@ class UnparsedSavedQuery(dbtClassMixin):
label: Optional[str] = None
exports: List[UnparsedExport] = field(default_factory=list)
config: Dict[str, Any] = field(default_factory=dict)
# Note: the order of the types is critical; it's the order that they will be checked against inputs.
# if reversed, a single-string tag like `tag: "good"` becomes ['g','o','o','d']
tags: Union[str, List[str]] = field(
default_factory=list_str,
metadata=metas(ShowBehavior.Hide, MergeBehavior.Append, CompareBehavior.Exclude),
)


def normalize_date(d: Optional[datetime.date]) -> Optional[datetime.datetime]:
Expand Down
13 changes: 13 additions & 0 deletions core/dbt/parser/schema_yaml_readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,18 @@ def parse_saved_query(self, unparsed: UnparsedSavedQuery) -> None:
rendered=False,
)

# The parser handles plain strings just fine, but we need to be able
# to join two lists, remove duplicates, and sort, so we have to wrap things here.
def wrap_tags(s: Union[List[str], str]) -> List[str]:
if s is None:
return []
return [s] if isinstance(s, str) else s

config_tags = wrap_tags(config.get("tags"))
unparsed_tags = wrap_tags(unparsed.tags)
tags = list(set([*unparsed_tags, *config_tags]))
tags.sort()

parsed = SavedQuery(
description=unparsed.description,
label=unparsed.label,
Expand All @@ -814,6 +826,7 @@ def parse_saved_query(self, unparsed: UnparsedSavedQuery) -> None:
config=config,
unrendered_config=unrendered_config,
group=config.group,
tags=tags,
)

for export in parsed.exports:
Expand Down
2 changes: 1 addition & 1 deletion schemas/dbt/catalog/v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"dbt_version": {
"type": "string",
"default": "1.9.0b2"
"default": "1.10.0a1"
},
"generated_at": {
"type": "string"
Expand Down
26 changes: 26 additions & 0 deletions schemas/dbt/manifest/v12.json
Original file line number Diff line number Diff line change
Expand Up @@ -19781,6 +19781,19 @@
"name"
]
}
},
"tags": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -21399,6 +21412,19 @@
"name"
]
}
},
"tags": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
}
},
"additionalProperties": false,
Expand Down
5 changes: 3 additions & 2 deletions schemas/dbt/run-results/v6.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"dbt_version": {
"type": "string",
"default": "1.9.0b2"
"default": "1.10.0a1"
},
"generated_at": {
"type": "string"
Expand Down Expand Up @@ -55,7 +55,8 @@
"success",
"error",
"skipped",
"partial success"
"partial success",
"no-op"
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion schemas/dbt/sources/v3.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"dbt_version": {
"type": "string",
"default": "1.9.0b2"
"default": "1.10.0a1"
},
"generated_at": {
"type": "string"
Expand Down
24 changes: 24 additions & 0 deletions tests/functional/saved_queries/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,27 @@
export_as: table
schema: my_export_schema_name
"""

saved_query_with_tags_defined_yml = """
saved_queries:
- name: test_saved_query
description: "{{ doc('saved_query_description') }}"
label: Test Saved Query
tags:
- tag_a
- tag_c
query_params:
metrics:
- simple_metric
group_by:
- "Dimension('id__ds')"
where:
- "{{ TimeDimension('id__ds', 'DAY') }} <= now()"
- "{{ TimeDimension('id__ds', 'DAY') }} >= '2023-01-01'"
exports:
- name: my_export
config:
alias: my_export_alias
export_as: table
schema: my_export_schema_name
"""
34 changes: 34 additions & 0 deletions tests/functional/saved_queries/test_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
saved_query_with_cache_configs_defined_yml,
saved_query_with_export_configs_defined_at_saved_query_level_yml,
saved_query_with_extra_config_attributes_yml,
saved_query_with_tags_defined_yml,
saved_query_without_export_configs_defined_yml,
)
from tests.functional.semantic_models.fixtures import (
Expand Down Expand Up @@ -322,3 +323,36 @@ def test_override_saved_query_config(
result = runner.invoke(["parse"])
assert result.success
assert saved_query.config.cache.enabled is True


# the tags defined in project yaml for the SavedQuery is additive to the query's
class TestSavedQueryTagsAdditiveWithConfig(BaseConfigProject):
@pytest.fixture(scope="class")
def project_config_update(self):
return {
"saved-queries": {"+tags": ["tag_b", "tag_c"]},
}

@pytest.fixture(scope="class")
def models(self):
return {
"saved_queries.yml": saved_query_with_tags_defined_yml,
"schema.yml": schema_yml,
"fct_revenue.sql": fct_revenue_sql,
"metricflow_time_spine.sql": metricflow_time_spine_sql,
"docs.md": saved_query_description,
}

def test_saved_query_tags_are_additive_unique_and_sorted(
self,
project,
):
runner = dbtTestRunner()

# parse with default fixture project config
result = runner.invoke(["parse"])
assert result.success
assert isinstance(result.result, Manifest)
assert len(result.result.saved_queries) == 1
saved_query = result.result.saved_queries["saved_query.test.test_saved_query"]
assert saved_query.tags == ["tag_a", "tag_b", "tag_c"]

0 comments on commit 97ffc37

Please sign in to comment.