-
-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add drawdb module * feat: complete code drawdb * test: add unit tests * docs: update mkdocs and readme
- Loading branch information
Showing
11 changed files
with
3,433 additions
and
459 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import json | ||
from typing import List, Tuple | ||
|
||
from dbterd.adapters import adapter | ||
from dbterd.adapters.meta import Table | ||
from dbterd.types import Catalog, Manifest | ||
|
||
|
||
def run(manifest: Manifest, catalog: Catalog, **kwargs) -> Tuple[str, str]: | ||
"""Parse dbt artifacts and export DDB file | ||
Args: | ||
manifest (dict): Manifest json | ||
catalog (dict): Catalog json | ||
Returns: | ||
Tuple(str, str): File name and the DDB (json) content | ||
""" | ||
output_file_name = kwargs.get("output_file_name") or "output.ddb" | ||
return (output_file_name, parse(manifest, catalog, **kwargs)) | ||
|
||
|
||
def parse(manifest: Manifest, catalog: Catalog, **kwargs) -> str: | ||
"""Get the DDB content from dbt artifacts | ||
Args: | ||
manifest (dict): Manifest json | ||
catalog (dict): Catalog json | ||
Returns: | ||
str: DDB (json) content | ||
""" | ||
|
||
algo_module = adapter.load_algo(name=kwargs["algo"]) | ||
tables, relationships = algo_module.parse( | ||
manifest=manifest, catalog=catalog, **kwargs | ||
) | ||
|
||
# Build DDB content | ||
graphic_tables = get_graphic_tables(tables=tables) | ||
drawdb = dict( | ||
author="dbterd", | ||
title=kwargs.get("output_file_name") or "Generated by dbterd", | ||
date=str(manifest.metadata.generated_at), | ||
tables=[ | ||
dict( | ||
id=idx, | ||
name=x.name, | ||
x=graphic_tables.get(x.name, {}).get("x"), | ||
y=graphic_tables.get(x.name, {}).get("y"), | ||
comment=x.description, | ||
indices=[], | ||
color="#175e7a", | ||
fields=[ | ||
dict( | ||
id=idc, | ||
name=c.name, | ||
type=c.data_type, | ||
default="", | ||
check="", | ||
primary=False, # TODO | ||
unique=False, # TODO | ||
notNull=False, # TODO | ||
increment=False, | ||
comment=c.description, | ||
) | ||
for idc, c in enumerate(x.columns) | ||
], | ||
) | ||
for idx, x in enumerate(tables) | ||
], | ||
relationships=[ | ||
dict( | ||
id=idx, | ||
name=f"fk__{x.table_map[1]}_{x.table_map[0]}__{x.column_map[1]}", | ||
cardinality=get_rel_symbol(x.type), | ||
startTableId=graphic_tables.get(x.table_map[1], {}).get("id"), | ||
endTableId=graphic_tables.get(x.table_map[0], {}).get("id"), | ||
startFieldId=( | ||
graphic_tables.get(x.table_map[1], {}) | ||
.get("fields") | ||
.get(x.column_map[1], {}) | ||
.get("id") | ||
), | ||
endFieldId=( | ||
graphic_tables.get(x.table_map[0], {}) | ||
.get("fields") | ||
.get(x.column_map[0], {}) | ||
.get("id") | ||
), | ||
updateConstraint="No action", | ||
deleteConstraint="No action", | ||
) | ||
for idx, x in enumerate(relationships) | ||
], | ||
notes=[], | ||
subjectAreas=[], | ||
database="generic", | ||
types=[], | ||
) | ||
|
||
return json.dumps(drawdb) | ||
|
||
|
||
def get_y( | ||
tables: List[Table], idx: int, graphic_tables: dict, column_size: int = 4 | ||
) -> float: | ||
"""Get y value of a table | ||
`y = S x (T's no of columns) + (T's y value if any)` | ||
- T: the prev table in the same graph column | ||
- S: the height value of a graphic column, default = 50 | ||
Args: | ||
tables (List[Table]): Parsed tables | ||
idx (int): Current table index | ||
graphic_tables (dict): Mutable caculated graphic tables dict | ||
column_size (int): Graphic column size, default = 4 | ||
Returns: | ||
float: y value | ||
""" | ||
if idx < column_size: | ||
return 0 | ||
|
||
col_len = len(tables[idx - column_size].columns) + 1 # plus title row | ||
y = (50 * col_len) * int(0 if idx < column_size else 1) | ||
|
||
if idx - column_size >= 0: | ||
prev_table_name = tables[idx - column_size].name | ||
y += graphic_tables[prev_table_name].get("y", 0) | ||
|
||
return y | ||
|
||
|
||
def get_graphic_tables(tables: List[Table]) -> dict: | ||
"""Return the indexed and pre-layouted tables | ||
Args: | ||
tables (List[Table]): List of parsed tables | ||
Returns: | ||
dict: Indexed and Layouted tables | ||
""" | ||
graphic_tables = dict() | ||
for idx, x in enumerate(tables): | ||
idx_fields = dict() | ||
graphic_tables[x.name] = dict( | ||
id=idx, | ||
x=500 * (idx % 4), | ||
y=get_y(tables, idx, graphic_tables), | ||
fields=idx_fields, | ||
) | ||
for idc, c in enumerate(x.columns): | ||
idx_fields[c.name] = dict(id=idc) | ||
|
||
return graphic_tables | ||
|
||
|
||
def get_rel_symbol(relationship_type: str) -> str: | ||
"""Get DDB relationship symbol | ||
Args: | ||
relationship_type (str): relationship type | ||
Returns: | ||
str: Relation symbol supported in DDB | ||
""" | ||
if relationship_type in ["01", "11"]: | ||
return "One to one" | ||
if relationship_type in ["0n", "1n"]: | ||
return "One to many" | ||
if relationship_type in ["nn"]: | ||
return "Many to many" | ||
return "Many to one" # n1 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# Generate DrawDB | ||
|
||
## 1. Produce dbt artifact files | ||
|
||
Let's use [Jaffle-Shop](https://github.com/dbt-labs/jaffle-shop) as the example. | ||
|
||
Clone it, then perform the `dbt docs generate` in order to generate the `/target` folder containing: | ||
|
||
- `manifest.json` | ||
- `catalog.json` | ||
|
||
Or we can use the generated files found in the [samples](https://github.com/datnguye/dbterd/tree/main/samples/jaffle-shop) | ||
|
||
## 2. Generate DrawDB (.ddb) file | ||
|
||
In the same dbt project directory, let's run `dbterd` command to generate the `.ddb` file which is the supported import file format, actually it is `json` file | ||
|
||
```shell | ||
dbterd run -t drawdb -enf table | ||
``` | ||
|
||
There we go, here is the sample output content: | ||
|
||
```json | ||
{ | ||
"author": "dbterd", | ||
"title": "erd", | ||
"date": "2024-07-28T01:54:24.620460Z", | ||
"tables": [ | ||
... | ||
{ | ||
"id": 3, | ||
"name": "order_items", | ||
... | ||
} | ||
... | ||
{ | ||
"id": 4, | ||
"name": "orders", | ||
... | ||
} | ||
], | ||
"relationships": [ | ||
{ | ||
"id": 0, | ||
"name": "fk__order_items_orders__order_id", | ||
"cardinality": "Many to one", | ||
"startTableId": 3, | ||
"endTableId": 4, | ||
"startFieldId": 1, | ||
"endFieldId": 0, | ||
... | ||
}, | ||
... | ||
``` | ||
|
||
> Check full sample at [samples/jaffle-shop/erd.ddb](https://github.com/datnguye/dbterd/blob/main/samples/jaffle-shop/erd.ddb) | ||
|
||
## 3. Import to Draw DB Editor | ||
|
||
Go to the [Draw DB Editor](https://drawdb.vercel.app/editor) playaround: | ||
|
||
- Files > Import diagram | ||
- Choose the generated file e.g. `erd.ddb` | ||
- Click `Import` | ||
|
||
Voila 🎉, here the result: | ||
|
||
![import-ddb.png](./../../../assets/images/import-ddb.png) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.