Skip to content

Commit

Permalink
Add docs as a real node config and support node_color for coloring …
Browse files Browse the repository at this point in the history
…the DAG (#5397)

* add Optional node_color config in Docs dataclass

* Remove node_color from the original docs config

* Add docs config and input validation

* Handle when docs is both under docs and config.docs

* Add node_color to Docs

* Make docs a Dict to avoid parsing errors

* Make docs a dataclass instead of a Dict

* Fix error when using docs as dataclass

* Simplify generator for the default value

* skeleton for test fixtures

* bump manifest to v7

* + config hierarchy tests

* add show override tests

* update manifest

* Remove node_color from the original docs config

* Add node_color to Docs

* Make docs a Dict to avoid parsing errors

* Make docs a dataclass instead of a Dict

* Simplify generator for the default value

* + config hierarchy tests

* add show override tests

* Fix unit tests

* Add tests in case of incorrect input for node_color

* Rename tests and Fix typos

* Fix functional tests

* Fix issues with remote branch

* Add changie entry

* modify tests to meet standards (#5608)

Co-authored-by: Matt Winkler <[email protected]>
Co-authored-by: Emily Rockman <[email protected]>
  • Loading branch information
3 people authored Aug 3, 2022
1 parent 7886924 commit 32415e3
Show file tree
Hide file tree
Showing 14 changed files with 7,058 additions and 27 deletions.
8 changes: 8 additions & 0 deletions .changes/unreleased/Features-20220803-104230.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
kind: Features
body: Make `docs` configurable in `dbt_project.yml` and add a `node_color` attribute
to change the color of nodes in the DAG
time: 2022-08-03T10:42:30.60624+02:00
custom:
Author: matt-winkler sungchun12 b-per
Issue: "5333"
PR: "5397"
19 changes: 17 additions & 2 deletions core/dbt/contracts/graph/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
ValidationError,
register_pattern,
)
from dbt.contracts.graph.unparsed import AdditionalPropertiesAllowed
from dbt.contracts.graph.unparsed import AdditionalPropertiesAllowed, Docs
from dbt.contracts.graph.utils import validate_color
from dbt.exceptions import InternalException, CompilationException
from dbt.contracts.util import Replaceable, list_str
from dbt import hooks
Expand Down Expand Up @@ -285,7 +286,7 @@ def same_contents(cls, unrendered: Dict[str, Any], other: Dict[str, Any]) -> boo
# 'meta' moved here from node
mergebehavior = {
"append": ["pre-hook", "pre_hook", "post-hook", "post_hook", "tags"],
"update": ["quoting", "column_types", "meta"],
"update": ["quoting", "column_types", "meta", "docs"],
"dict_key_append": ["grants"],
}

Expand Down Expand Up @@ -465,6 +466,20 @@ class NodeConfig(NodeAndTestConfig):
default_factory=list,
metadata=MergeBehavior.Append.meta(),
)
docs: Docs = field(
default_factory=Docs,
metadata=MergeBehavior.Update.meta(),
)

# we validate that node_color has a suitable value to prevent dbt-docs from crashing
def __post_init__(self):
if self.docs.node_color:
node_color = self.docs.node_color
if not validate_color(node_color):
raise ValidationError(
f"Invalid color name for docs.node_color: {node_color}. "
"It is neither a valid HTML color name nor a valid HEX code."
)

@classmethod
def __pre_deserialize__(cls, data):
Expand Down
1 change: 0 additions & 1 deletion core/dbt/contracts/graph/parsed.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ def patch(self, patch: "ParsedNodePatch"):
self.created_at = time.time()
self.description = patch.description
self.columns = patch.columns
self.docs = patch.docs

def get_materialization(self):
return self.config.materialized
Expand Down
1 change: 1 addition & 0 deletions core/dbt/contracts/graph/unparsed.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class UnparsedRunHook(UnparsedNode):
@dataclass
class Docs(dbtClassMixin, Replaceable):
show: bool = True
node_color: Optional[str] = None


@dataclass
Expand Down
153 changes: 153 additions & 0 deletions core/dbt/contracts/graph/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import re

HTML_COLORS = [
"aliceblue",
"antiquewhite",
"aqua",
"aquamarine",
"azure",
"beige",
"bisque",
"black",
"blanchedalmond",
"blue",
"blueviolet",
"brown",
"burlywood",
"cadetblue",
"chartreuse",
"chocolate",
"coral",
"cornflowerblue",
"cornsilk",
"crimson",
"cyan",
"darkblue",
"darkcyan",
"darkgoldenrod",
"darkgray",
"darkgreen",
"darkkhaki",
"darkmagenta",
"darkolivegreen",
"darkorange",
"darkorchid",
"darkred",
"darksalmon",
"darkseagreen",
"darkslateblue",
"darkslategray",
"darkturquoise",
"darkviolet",
"deeppink",
"deepskyblue",
"dimgray",
"dodgerblue",
"firebrick",
"floralwhite",
"forestgreen",
"fuchsia",
"gainsboro",
"ghostwhite",
"gold",
"goldenrod",
"gray",
"green",
"greenyellow",
"honeydew",
"hotpink",
"indianred",
"indigo",
"ivory",
"khaki",
"lavender",
"lavenderblush",
"lawngreen",
"lemonchiffon",
"lightblue",
"lightcoral",
"lightcyan",
"lightgoldenrodyellow",
"lightgray",
"lightgreen",
"lightpink",
"lightsalmon",
"lightsalmon",
"lightseagreen",
"lightskyblue",
"lightslategray",
"lightsteelblue",
"lightyellow",
"lime",
"limegreen",
"linen",
"magenta",
"maroon",
"mediumaquamarine",
"mediumblue",
"mediumorchid",
"mediumpurple",
"mediumseagreen",
"mediumslateblue",
"mediumslateblue",
"mediumspringgreen",
"mediumturquoise",
"mediumvioletred",
"midnightblue",
"mintcream",
"mistyrose",
"moccasin",
"navajowhite",
"navy",
"oldlace",
"olive",
"olivedrab",
"orange",
"orangered",
"orchid",
"palegoldenrod",
"palegreen",
"paleturquoise",
"palevioletred",
"papayawhip",
"peachpuff",
"peru",
"pink",
"plum",
"powderblue",
"purple",
"rebeccapurple",
"red",
"rosybrown",
"royalblue",
"saddlebrown",
"salmon",
"sandybrown",
"seagreen",
"seashell",
"sienna",
"silver",
"skyblue",
"slateblue",
"slategray",
"snow",
"springgreen",
"steelblue",
"tan",
"teal",
"thistle",
"tomato",
"turquoise",
"violet",
"wheat",
"white",
"whitesmoke",
"yellow",
"yellowgreen",
]


def validate_color(color: str) -> bool:
match_hex = re.search(r"^#(?:[0-9a-f]{3}){1,2}$", color.lower())
match_html_color_name = color.lower() in HTML_COLORS
return bool(match_hex or match_html_color_name)
18 changes: 17 additions & 1 deletion core/dbt/parser/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from dbt.context.context_config import ContextConfig
from dbt.contracts.graph.manifest import Manifest
from dbt.contracts.graph.parsed import HasUniqueID, ManifestNodes
from dbt.contracts.graph.unparsed import UnparsedNode
from dbt.contracts.graph.unparsed import UnparsedNode, Docs
from dbt.exceptions import ParsingException, validator_error_message, InternalException
from dbt import hooks
from dbt.node_types import NodeType, ModelLanguage
Expand Down Expand Up @@ -291,6 +291,22 @@ def update_parsed_node_config(
if "meta" in config_dict and config_dict["meta"]:
parsed_node.meta = config_dict["meta"]

# If we have docs in the config, merge with the node level, for backwards
# compatibility with earlier node-only config.
if "docs" in config_dict and config_dict["docs"]:
# we set show at the value of the config if it is set, otherwize, inherit the value
docs_show = (
config_dict["docs"]["show"]
if "show" in config_dict["docs"]
else parsed_node.docs.show
)
if "node_color" in config_dict["docs"]:
parsed_node.docs = Docs(
show=docs_show, node_color=config_dict["docs"]["node_color"]
)
else:
parsed_node.docs = Docs(show=docs_show)

# unrendered_config is used to compare the original database/schema/alias
# values and to handle 'same_config' and 'same_contents' calls
parsed_node.unrendered_config = config.build_config_dict(rendered=False)
Expand Down
19 changes: 13 additions & 6 deletions core/dbt/parser/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,28 +808,35 @@ def get_unparsed_target(self) -> Iterable[NonSourceTarget]:
if self.key != "macros":
# macros don't have the 'config' key support yet
self.normalize_meta_attribute(data, path)
self.normalize_docs_attribute(data, path)
node = self._target_type().from_dict(data)
except (ValidationError, JSONValidationException) as exc:
msg = error_context(path, self.key, data, exc)
raise ParsingException(msg) from exc
else:
yield node

# We want to raise an error if 'meta' is in two places, and move 'meta'
# We want to raise an error if some attributes are in two places, and move them
# from toplevel to config if necessary
def normalize_meta_attribute(self, data, path):
if "meta" in data:
if "config" in data and "meta" in data["config"]:
def normalize_attribute(self, data, path, attribute):
if attribute in data:
if "config" in data and attribute in data["config"]:
raise ParsingException(
f"""
In {path}: found meta dictionary in 'config' dictionary and as top-level key.
In {path}: found {attribute} dictionary in 'config' dictionary and as top-level key.
Remove the top-level key and define it under 'config' dictionary only.
""".strip()
)
else:
if "config" not in data:
data["config"] = {}
data["config"]["meta"] = data.pop("meta")
data["config"][attribute] = data.pop(attribute)

def normalize_meta_attribute(self, data, path):
return self.normalize_attribute(data, path, "meta")

def normalize_docs_attribute(self, data, path):
return self.normalize_attribute(data, path, "docs")

def patch_node_config(self, node, patch):
# Get the ContextConfig that's used in calculating the config
Expand Down
6,461 changes: 6,460 additions & 1 deletion schemas/dbt/manifest/v7.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/unit/test_contracts_graph_compiled.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ def basic_compiled_dict():
'meta': {},
'grants': {},
'packages': [],
'docs': {'show': True},
},
'docs': {'show': True},
'columns': {},
Expand Down
Loading

0 comments on commit 32415e3

Please sign in to comment.