Skip to content

Commit

Permalink
feat: add GraphViz and D2 as new outputs (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
datnguye authored May 5, 2023
1 parent 0c1bea5 commit c9504b0
Show file tree
Hide file tree
Showing 18 changed files with 1,010 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# dbterd

CLI to generate Diagram-as-a-code file ([DBML](https://dbdiagram.io/d), [Mermaid](https://mermaid-js.github.io/mermaid-live-editor/), [PlantUML](https://plantuml.com/ie-diagram)) from dbt artifact files (required: `manifest.json`, `catalog.json`)
CLI to generate Diagram-as-a-code file ([DBML](https://dbdiagram.io/d), [Mermaid](https://mermaid-js.github.io/mermaid-live-editor/), [PlantUML](https://plantuml.com/ie-diagram), [GraphViz](https://graphviz.org/), [D2](https://d2lang.com/)) from dbt artifact files (required: `manifest.json`, `catalog.json`)

[![PyPI version](https://badge.fury.io/py/dbterd.svg)](https://pypi.org/project/dbterd/)
![python-cli](https://img.shields.io/badge/CLI-Python-FFCE3E?labelColor=14354C&logo=python&logoColor=white)
Expand Down
2 changes: 2 additions & 0 deletions dbterd/adapters/targets/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ class Strategies:
DBML_TEST_RELATIONSHIP = "dbml_test_relationship"
MERMAID_TEST_RELATIONSHIP = "mermaid_test_relationship"
PLANTUML_TEST_RELATIONSHIP = "plantuml_test_relationship"
D2_TEST_RELATIONSHIP = "d2_test_relationship"
GRAPHVIZ_TEST_RELATIONSHIP = "graphviz_test_relationship"
8 changes: 8 additions & 0 deletions dbterd/adapters/targets/d2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from dbterd.adapters.targets.constants import Strategies
from dbterd.adapters.targets.d2 import d2_test_relationship
from dbterd.adapters.targets.default import default

run_operation_default = default
run_operation_dispatcher = {
Strategies.D2_TEST_RELATIONSHIP: d2_test_relationship.run,
}
58 changes: 58 additions & 0 deletions dbterd/adapters/targets/d2/d2_test_relationship.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from dbterd.adapters.algos import test_relationship


def run(manifest, catalog, **kwargs):
"""Parse dbt artifacts and export D2 file
Args:
manifest (dict): Manifest json
catalog (dict): Catalog json
Returns:
Tuple(str, str): File name and the D2 content
"""
return ("output.d2", parse(manifest, catalog, **kwargs))


def parse(manifest, catalog, **kwargs):
"""Get the D2 content from dbt artifacts
Args:
manifest (dict): Manifest json
catalog (dict): Catalog json
Returns:
str: D2 content
"""
tables, relationships = test_relationship.parse(
manifest=manifest, catalog=catalog, **kwargs
)

# Build D2 content
# https://play.d2lang.com/?script=qlDQtVOo5AIEAAD__w%3D%3D&, https://github.com/terrastruct/d2
d2 = ""
for table in tables:
d2 += '"{table}": {{\n shape: sql_table\n{columns}\n}}\n'.format(
table=table.name,
columns="\n".join([f" {x.name}: {x.data_type}" for x in table.columns]),
)

for rel in relationships:
key_from = f'"{rel.table_map[1]}"'
key_to = f'"{rel.table_map[0]}"'
connector = f"{rel.column_map[1]} = {rel.column_map[0]}"
d2 += f'{key_from} {get_rel_symbol(rel.type)} {key_to}: "{connector}"\n'

return d2


def get_rel_symbol(relationship_type: str) -> str:
"""Get D2 relationship symbol
Args:
relationship_type (str): relationship type
Returns:
str: Relation symbol supported in D2
"""
return "->" # no supports for rel type
2 changes: 1 addition & 1 deletion dbterd/adapters/targets/dbml/dbml_test_relationship.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def parse(manifest, catalog, **kwargs):
dbml = "//Tables (based on the selection criteria)\n"
for table in tables:
dbml += f"//--configured at schema: {table.database}.{table.schema}\n"
dbml += """Table \"{table}\" {{\n{columns}\n}}\n""".format(
dbml += 'Table "{table}" {{\n{columns}\n}}\n'.format(
table=table.name,
columns="\n".join(
[
Expand Down
8 changes: 8 additions & 0 deletions dbterd/adapters/targets/graphviz/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from dbterd.adapters.targets.constants import Strategies
from dbterd.adapters.targets.default import default
from dbterd.adapters.targets.graphviz import graphviz_test_relationship

run_operation_default = default
run_operation_dispatcher = {
Strategies.GRAPHVIZ_TEST_RELATIONSHIP: graphviz_test_relationship.run,
}
91 changes: 91 additions & 0 deletions dbterd/adapters/targets/graphviz/graphviz_test_relationship.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
from dbterd.adapters.algos import test_relationship


def run(manifest, catalog, **kwargs):
"""Parse dbt artifacts and export GraphViz file
Args:
manifest (dict): Manifest json
catalog (dict): Catalog json
Returns:
Tuple(str, str): File name and the GraphViz content
"""
return ("output.graphviz", parse(manifest, catalog, **kwargs))


def parse(manifest, catalog, **kwargs):
"""Get the GraphViz content from dbt artifacts
Args:
manifest (dict): Manifest json
catalog (dict): Catalog json
Returns:
str: GraphViz content
"""
tables, relationships = test_relationship.parse(
manifest=manifest, catalog=catalog, **kwargs
)

# Build GraphViz content
# https://dreampuf.github.io/GraphvizOnline/, https://graphviz.org/
graphviz = (
"digraph g {\n"
' fontname="Helvetica,Arial,sans-serif"\n'
' node [fontname="Helvetica,Arial,sans-serif"]\n'
' edge [fontname="Helvetica,Arial,sans-serif"]\n'
' graph [fontsize=30 labelloc="t" label="" splines=true overlap=false rankdir="LR"];\n'
" ratio=auto;\n"
)
for table in tables:
graphviz += (
' "{table}" [\n'
' style = "filled, bold"\n'
" penwidth = 1\n"
' fillcolor = "white"\n'
' fontname = "Courier New"\n'
' shape = "Mrecord"\n'
" label =<\n"
' <table border="0" cellborder="0" cellpadding="3" bgcolor="white">\n'
' <tr><td bgcolor="black" align="center" colspan="2">'
'<font color="white">{table}</font></td></tr>\n{columns}\n'
" </table>> ];\n"
).format(
table=table.name,
columns="\n".join(
[
f' <tr><td align="left">({x.data_type}) {x.name}</td></tr>'
for x in table.columns
]
),
)

for rel in relationships:
key_from = f'"{rel.table_map[1]}"'
key_to = f'"{rel.table_map[0]}"'
connector = f"{rel.column_map[1]} = {rel.column_map[0]}"
graphviz += (
f" {key_from} {get_rel_symbol(rel.type)} {key_to} [ \n"
" penwidth = 1\n"
" fontsize = 12\n"
' fontcolor = "black"\n'
f' label = "{connector}"\n'
" ];\n"
)

graphviz += "}"

return graphviz


def get_rel_symbol(relationship_type: str) -> str:
"""Get GraphViz relationship symbol
Args:
relationship_type (str): relationship type
Returns:
str: Relation symbol supported in GraphViz
"""
return "->" # no supports for rel type
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def parse(manifest, catalog, **kwargs):
# https://mermaid.js.org/syntax/entityRelationshipDiagram.html
mermaid = "erDiagram\n"
for table in tables:
mermaid += """ \"{table}\" {{\n{columns}\n }}\n""".format(
mermaid += ' "{table}" {{\n{columns}\n }}\n'.format(
table=table.name.upper(),
columns="\n".join(
[
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# dbterd

CLI to generate Diagram-as-a-code file ([DBML](https://dbdiagram.io/d), [Mermaid](https://mermaid-js.github.io/mermaid-live-editor/), [PlantUML](https://plantuml.com/ie-diagram)) from dbt artifact files (required: `manifest.json`, `catalog.json`)
CLI to generate Diagram-as-a-code file ([DBML](https://dbdiagram.io/d), [Mermaid](https://mermaid-js.github.io/mermaid-live-editor/), [PlantUML](https://plantuml.com/ie-diagram), [GraphViz](https://graphviz.org/), [D2](https://d2lang.com/)) from dbt artifact files (required: `manifest.json`, `catalog.json`)

[![PyPI version](https://badge.fury.io/py/dbterd.svg)](https://pypi.org/project/dbterd/)
![python-cli](https://img.shields.io/badge/CLI-Python-FFCE3E?labelColor=14354C&logo=python&logoColor=white)
Expand Down
38 changes: 38 additions & 0 deletions docs/nav/guide/targets/generate-d2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generate D2 ERD

## 1. Install D2 CLI

```bash
# With --dry-run the install script will print the commands it will use
# to install without actually installing so you know what it's going to do.
curl -fsSL https://d2lang.com/install.sh | sh -s -- --dry-run
# If things look good, install for real.
curl -fsSL https://d2lang.com/install.sh | sh -s --
```

> Check [installations](https://github.com/terrastruct/d2/blob/master/docs/INSTALL.md) for more details
## 2. Generate D2 ERD content

```bash
dbterd run -t d2 -ad "samples/dbtresto" -o "target" -s schema:dbt.mart
# Expected: ./target/output.d2
```

## 2. Export to SVG

```bash
d2 -w ./target/output.d2 ./target/output.svg
```

### 3. Embeded into Markdown

```markdown
# Sample D2 ERD

![d2](./target/output.svg)
```

Sample Output:

![d2](https://github.com/datnguye/dbterd/blob/main/samples/dbtresto/d2.svg)
16 changes: 11 additions & 5 deletions docs/nav/guide/targets/generate-dbml.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
# Generate DBML

#### 1. Produce your manifest json
## 1. Produce your manifest json

In your dbt project (I am using dbt-resto/[integration_tests](https://github.com/datnguye/dbt-resto) for demo purpose), try to build the docs:

```bash
dbt docs generate
```

#### 2. Generate DBML
## 2. Generate DBML

Copy `manifest.json` and `catalog.json` into a specific folder OR do nothing and let's assume we're using `dbt/target` directory, and run
```

```bash
dbterd run -ad "/path/to/dbt/target" -o "/path/to/output"
# dbterd run -ad "samples/dbtresto" -s model.dbt_resto -ns model.dbt_resto.staging
```

File `./target/output.dbml` will be generated as the result

#### 3. Build database docs site (Optional)
## 3. Build database docs site (Optional)

Assuming you're already familiar with [dbdocs](https://dbdocs.io/docs#installation)
```

```bash
dbdocs build "/path/to/output/output.dbml"
# dbdocs build "./target/output.dbml"
```

Your terminal should provide the info as below:

```bash
√ Parsing file content
? Project name: poc
Expand Down
34 changes: 34 additions & 0 deletions docs/nav/guide/targets/generate-graphviz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generate GraphViz ERD

## 1. Install GraphViz CLI

```bash
sudo apt install graphviz
```

> Check [installations](https://graphviz.org/download/#linux) for more details
## 2. Generate GraphViz ERD content

```bash
dbterd run -t graphviz -ad "samples/dbtresto" -o "target" -s schema:dbt.mart
# Expected: ./target/output.graphviz
```

## 2. Export to PNG

```bash
dot -Tpng ./target/output.d2 > ./target/output.png
```

### 3. Embeded into Markdown

```markdown
# Sample GraphViz ERD

![graphviz](./target/output.png)
```

Sample Output:

![graphviz](https://github.com/datnguye/dbterd/blob/main/samples/dbtresto/graphviz.png)
8 changes: 5 additions & 3 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ nav:
- User Guide:
- CLI Reference: nav/guide/cli-references.md
- Generate the Targets:
- Generate DBML: nav/guide/targets/generate-dbml.md
- Generate Mermaid: nav/guide/targets/generate-markdown-mermaid-erd.md
- Generate PlantUML: nav/guide/targets/generate-plantuml.md
- DBML: nav/guide/targets/generate-dbml.md
- Mermaid: nav/guide/targets/generate-markdown-mermaid-erd.md
- PlantUML: nav/guide/targets/generate-plantuml.md
- D2: nav/guide/targets/generate-d2.md
- GraphViz: nav/guide/targets/generate-graphviz.md
- Metadata:
- Ignore Tests: nav/metadata/ignore_in_erd.md
- Relationship Types: nav/metadata/relationship_type.md
Expand Down
Loading

0 comments on commit c9504b0

Please sign in to comment.