docs: add dynamic entity-relationship diagram to docs (#28130)

This commit is contained in:
Maxime Beauchemin 2024-04-23 16:12:42 -07:00 committed by GitHub
parent f155138659
commit 9db431b430
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1114 additions and 6 deletions

View File

@ -23,7 +23,22 @@ jobs:
# compatible/incompatible licenses addressed here: https://www.apache.org/legal/resolved.html
# find SPDX identifiers here: https://spdx.org/licenses/
deny-licenses: MS-LPL, BUSL-1.1, QPL-1.0, Sleepycat, SSPL-1.0, CPOL-1.02, AGPL-3.0, GPL-1.0+, BSD-4-Clause-UC, NPL-1.0, NPL-1.1, JSON
# adding an exception for an ambigious license on store2, which has been resolved in the latest version. It's MIT: https://github.com/nbubna/store/blob/master/LICENSE-MIT
# adding exception for all applitools modules (eyes-cypress and its dependencies), which has an explicit OSS license approved by ASF
# license: https://applitools.com/legal/open-source-terms-of-use/
allow-dependencies-licenses: 'pkg:npm/store2@2.14.2, pkg:npm/applitools/core, pkg:npm/applitools/core-base, pkg:npm/applitools/css-tree, pkg:npm/applitools/ec-client, pkg:npm/applitools/eg-socks5-proxy-server, pkg:npm/applitools/eyes, pkg:npm/applitools/eyes-cypress, pkg:npm/applitools/nml-client, pkg:npm/applitools/tunnel-client, pkg:npm/applitools/utils'
allow-dependencies-licenses:
# adding an exception for an ambigious license on store2, which has been resolved in
# the latest version. It's MIT: https://github.com/nbubna/store/blob/master/LICENSE-MIT
- 'pkg:npm/store2@2.14.2'
# adding exception for all applitools modules (eyes-cypress and its dependencies),
# which has an explicit OSS license approved by ASF
# license: https://applitools.com/legal/open-source-terms-of-use/
- 'pkg:npm/applitools/core'
- 'pkg:npm/applitools/core-base'
- 'pkg:npm/applitools/css-tree'
- 'pkg:npm/applitools/ec-client'
- 'pkg:npm/applitools/eg-socks5-proxy-server'
- 'pkg:npm/applitools/eyes'
- 'pkg:npm/applitools/eyes-cypress'
- 'pkg:npm/applitools/nml-client'
- 'pkg:npm/applitools/tunnel-client'
- 'pkg:npm/applitools/utils'
# Selecting BSD-3-Clause licensing terms for node-forge to ensure compatibility with Apache
- 'pkg:npm/node-forge@1.3.1'

View File

@ -39,6 +39,13 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Setup Python
uses: ./.github/actions/setup-backend/
- name: Compute Entity Relationship diagram (ERD)
run: |
python scripts/erd.py
curl -L http://sourceforge.net/projects/plantuml/files/1.2023.7/plantuml.1.2023.7.jar/download > ~/plantuml.jar
java -jar ~/plantuml.jar -v -tsvg -r -o "${{ github.workspace }}/docs/static/img/erd.svg" "${{ github.workspace }}/scripts/erd/erd.puml"
- name: yarn install
run: |
yarn install --check-cache

View File

@ -66,3 +66,7 @@ google-big-query.svg
google-sheets.svg
postgresql.svg
snowflake.svg
# docs-related
erd.puml
erd.svg

View File

@ -0,0 +1,11 @@
import InteractiveSVG from '../../src/components/InteractiveERDSVG';
# Entity-Relationship Diagram
Here is our interactive ERD:
<InteractiveSVG />
<br />
[Download the .svg](https://github.com/apache/superset/tree/master/docs/static/img/erd.svg)

View File

@ -39,6 +39,7 @@
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-github-btn": "^1.4.0",
"react-svg-pan-zoom": "^3.12.1",
"stream": "^0.0.2",
"swagger-ui-react": "^4.1.3",
"url-loader": "^4.1.1"

View File

@ -0,0 +1,38 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { UncontrolledReactSVGPanZoom } from 'react-svg-pan-zoom';
import ErdSvg from '../../static/img/erd.svg';
function InteractiveERDSVG() {
return (
<UncontrolledReactSVGPanZoom
width="100%"
height="800"
background="#003153"
tool="auto"
>
<svg>
<ErdSvg />
</svg>
</UncontrolledReactSVGPanZoom>
);
}
export default InteractiveERDSVG;

1
docs/static/img/erd.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 177 KiB

View File

@ -1994,7 +1994,7 @@
"@docusaurus/theme-search-algolia" "2.4.3"
"@docusaurus/types" "2.4.3"
"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2":
"@docusaurus/react-loadable@5.5.2":
version "5.5.2"
resolved "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz"
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
@ -8223,6 +8223,15 @@ prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
object-assign "^4.1.1"
react-is "^16.8.1"
prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
property-information@^5.0.0, property-information@^5.3.0:
version "5.6.0"
resolved "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz"
@ -8841,7 +8850,7 @@ react-inspector@^5.1.1:
is-dom "^1.0.0"
prop-types "^15.0.0"
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -8878,6 +8887,14 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1:
dependencies:
"@babel/runtime" "^7.10.3"
"react-loadable@npm:@docusaurus/react-loadable@5.5.2":
version "5.5.2"
resolved "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz"
integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==
dependencies:
"@types/react" "*"
prop-types "^15.6.2"
react-redux@^7.2.4:
version "7.2.6"
resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz"
@ -8925,6 +8942,14 @@ react-router@5.3.4, react-router@^5.3.3:
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-svg-pan-zoom@^3.12.1:
version "3.12.1"
resolved "https://registry.yarnpkg.com/react-svg-pan-zoom/-/react-svg-pan-zoom-3.12.1.tgz#971de6163fbad0d2a98d3ad7eb09bd1941564376"
integrity sha512-ug1LHCN5qed56C64xFypr/ClajuMFkig1OKvwJrIgGeSyHOjWM7XGgSgeP3IfHAkNw8QEc6a31ggZRpTijWYRw==
dependencies:
prop-types "^15.8.1"
transformation-matrix "^2.14.0"
react-syntax-highlighter@^15.4.5:
version "15.4.5"
resolved "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.4.5.tgz"
@ -10069,6 +10094,11 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
transformation-matrix@^2.14.0:
version "2.16.1"
resolved "https://registry.yarnpkg.com/transformation-matrix/-/transformation-matrix-2.16.1.tgz#4a2de06331b94ae953193d1b9a5ba002ec5f658a"
integrity sha512-tdtC3wxVEuzU7X/ydL131Q3JU5cPMEn37oqVLITjRDSDsnSHVFzW2JiCLfZLIQEgWzZHdSy3J6bZzvKEN24jGA==
traverse@~0.6.6:
version "0.6.6"
resolved "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz"

676
scripts/erd/erd.puml Normal file
View File

@ -0,0 +1,676 @@
@startuml erd
title Apache Superset ERD
!theme blueprint
' avoid problems with angled crows feet
skinparam linetype ortho
skinparam classBorderColor #grey
skinparam classBorderColor<<new>> #white
skinparam classBorderThickness<<new>> 1
skinparam classLineStyle<<new>> Dashed
skinparam ClassBackgroundColor<<new>> #204143
' Models
rectangle "Data Assets" #black {
entity "SqlMetric (sql_metrics)" as sql_metrics {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
metric_name: VARCHAR(255)
verbose_name: VARCHAR(1024)
metric_type: VARCHAR(32)
description: TEXT
d3format: VARCHAR(128)
currency: VARCHAR(128)
warning_text: TEXT
table_id: INTEGER
expression: TEXT
extra: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "DatabaseUserOAuth2Tokens (database_user_oauth2_tokens)" as database_user_oauth2_tokens {
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
user_id: INTEGER
database_id: INTEGER
access_token: BLOB
access_token_expiration: DATETIME
refresh_token: BLOB
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "Table (sl_tables)" as sl_tables {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
extra_json: TEXT
id: INTEGER
database_id: INTEGER
catalog: TEXT
schema: TEXT
name: TEXT
is_managed_externally: BOOLEAN
external_url: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "Database (dbs)" as dbs {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
verbose_name: VARCHAR(250)
database_name: VARCHAR(250)
sqlalchemy_uri: VARCHAR(1024)
password: BLOB
cache_timeout: INTEGER
select_as_create_table_as: BOOLEAN
expose_in_sqllab: BOOLEAN
configuration_method: VARCHAR(255)
allow_run_async: BOOLEAN
allow_file_upload: BOOLEAN
allow_ctas: BOOLEAN
allow_cvas: BOOLEAN
allow_dml: BOOLEAN
force_ctas_schema: VARCHAR(250)
extra: TEXT
encrypted_extra: BLOB
impersonate_user: BOOLEAN
server_cert: BLOB
is_managed_externally: BOOLEAN
external_url: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "Dataset (sl_datasets)" as sl_datasets {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
extra_json: TEXT
id: INTEGER
database_id: INTEGER
is_physical: BOOLEAN
is_managed_externally: BOOLEAN
name: TEXT
expression: TEXT
external_url: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "SqlaTable (tables)" as tables {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
description: TEXT
default_endpoint: TEXT
is_featured: BOOLEAN
filter_select_enabled: BOOLEAN
offset: INTEGER
cache_timeout: INTEGER
params: VARCHAR(1000)
perm: VARCHAR(1000)
schema_perm: VARCHAR(1000)
is_managed_externally: BOOLEAN
external_url: TEXT
table_name: VARCHAR(250)
main_dttm_col: VARCHAR(250)
database_id: INTEGER
fetch_values_predicate: TEXT
schema: VARCHAR(255)
sql: TEXT
is_sqllab_view: BOOLEAN
template_params: TEXT
extra: TEXT
normalize_columns: BOOLEAN
always_filter_main_dttm: BOOLEAN
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "TableColumn (table_columns)" as table_columns {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
column_name: VARCHAR(255)
verbose_name: VARCHAR(1024)
is_active: BOOLEAN
type: TEXT
advanced_data_type: VARCHAR(255)
groupby: BOOLEAN
filterable: BOOLEAN
description: TEXT
table_id: INTEGER
is_dttm: BOOLEAN
expression: TEXT
python_date_format: VARCHAR(255)
extra: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "Column (sl_columns)" as sl_columns {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
extra_json: TEXT
id: INTEGER
is_additive: BOOLEAN
is_aggregation: BOOLEAN
is_filterable: BOOLEAN
is_dimensional: BOOLEAN
is_increase_desired: BOOLEAN
is_managed_externally: BOOLEAN
is_partition: BOOLEAN
is_physical: BOOLEAN
is_spatial: BOOLEAN
is_temporal: BOOLEAN
name: TEXT
type: TEXT
advanced_data_type: TEXT
expression: TEXT
unit: TEXT
description: TEXT
warning_text: TEXT
external_url: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "RowLevelSecurityFilter (row_level_security_filters)" as row_level_security_filters {
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
name: VARCHAR(255)
description: TEXT
filter_type: VARCHAR(7)
group_key: VARCHAR(255)
clause: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
}
rectangle "System" #black {
entity "KeyValueEntry (key_value)" as key_value {
uuid: BINARY(16)
id: INTEGER
resource: VARCHAR(32)
value: BLOB
created_on: DATETIME
created_by_fk: INTEGER
changed_on: DATETIME
expires_on: DATETIME
changed_by_fk: INTEGER
}
entity "SSHTunnel (ssh_tunnels)" as ssh_tunnels {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
extra_json: TEXT
id: INTEGER
database_id: INTEGER
server_address: TEXT
server_port: INTEGER
username: BLOB
password: BLOB
private_key: BLOB
private_key_password: BLOB
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "KeyValue (keyvalue)" as keyvalue {
id: INTEGER
value: TEXT
}
entity "CacheKey (cache_keys)" as cache_keys {
id: INTEGER
cache_key: VARCHAR(256)
cache_timeout: INTEGER
datasource_uid: VARCHAR(64)
created_on: DATETIME
}
entity "Log (logs)" as logs {
id: INTEGER
action: VARCHAR(512)
user_id: INTEGER
dashboard_id: INTEGER
slice_id: INTEGER
json: TEXT
dttm: DATETIME
duration_ms: INTEGER
referrer: VARCHAR(1024)
}
}
rectangle "SQL Lab" #black {
entity "SavedQuery (saved_query)" as saved_query {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
extra_json: TEXT
id: INTEGER
user_id: INTEGER
db_id: INTEGER
schema: VARCHAR(128)
label: VARCHAR(256)
description: TEXT
sql: TEXT
template_parameters: TEXT
rows: INTEGER
last_run: DATETIME
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "TableSchema (table_schema)" as table_schema {
created_on: DATETIME
changed_on: DATETIME
extra_json: TEXT
id: INTEGER
tab_state_id: INTEGER
database_id: INTEGER
schema: VARCHAR(256)
table: VARCHAR(256)
description: TEXT
expanded: BOOLEAN
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "Query (query)" as query {
tracking_url: TEXT
extra_json: TEXT
id: INTEGER
client_id: VARCHAR(11)
database_id: INTEGER
tmp_table_name: VARCHAR(256)
tmp_schema_name: VARCHAR(256)
user_id: INTEGER
status: VARCHAR(16)
tab_name: VARCHAR(256)
sql_editor_id: VARCHAR(256)
schema: VARCHAR(256)
sql: TEXT
select_sql: TEXT
executed_sql: TEXT
limit: INTEGER
limiting_factor: VARCHAR(18)
select_as_cta: BOOLEAN
select_as_cta_used: BOOLEAN
ctas_method: VARCHAR(16)
progress: INTEGER
rows: INTEGER
error_message: TEXT
results_key: VARCHAR(64)
start_time: NUMERIC(20, 6)
start_running_time: NUMERIC(20, 6)
end_time: NUMERIC(20, 6)
end_result_backend_time: NUMERIC(20, 6)
changed_on: DATETIME
}
entity "TabState (tab_state)" as tab_state {
created_on: DATETIME
changed_on: DATETIME
extra_json: TEXT
id: INTEGER
user_id: INTEGER
label: VARCHAR(256)
active: BOOLEAN
database_id: INTEGER
schema: VARCHAR(256)
sql: TEXT
query_limit: INTEGER
latest_query_id: INTEGER
autorun: BOOLEAN
template_params: TEXT
hide_left_bar: BOOLEAN
saved_query_id: INTEGER
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
}
rectangle "Core" #black {
entity "FavStar (favstar)" as favstar {
id: INTEGER
user_id: INTEGER
class_name: VARCHAR(50)
obj_id: INTEGER
dttm: DATETIME
}
entity "Dashboard (dashboards)" as dashboards {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
dashboard_title: VARCHAR(500)
position_json: TEXT
description: TEXT
css: TEXT
certified_by: TEXT
certification_details: TEXT
json_metadata: TEXT
slug: VARCHAR(255)
published: BOOLEAN
is_managed_externally: BOOLEAN
external_url: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "Annotation (annotation)" as annotation {
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
start_dttm: DATETIME
end_dttm: DATETIME
layer_id: INTEGER
short_descr: VARCHAR(500)
long_descr: TEXT
json_metadata: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "EmbeddedDashboard (embedded_dashboards)" as embedded_dashboards {
created_on: DATETIME
changed_on: DATETIME
uuid: BINARY(16)
allow_domain_list: TEXT
dashboard_id: INTEGER
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "Slice (slices)" as slices {
uuid: BINARY(16)
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
slice_name: VARCHAR(250)
datasource_id: INTEGER
datasource_type: VARCHAR(200)
datasource_name: VARCHAR(2000)
viz_type: VARCHAR(250)
params: TEXT
query_context: TEXT
description: TEXT
cache_timeout: INTEGER
perm: VARCHAR(1000)
schema_perm: VARCHAR(1000)
last_saved_at: DATETIME
last_saved_by_fk: INTEGER
certified_by: TEXT
certification_details: TEXT
is_managed_externally: BOOLEAN
external_url: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "TaggedObject (tagged_object)" as tagged_object {
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
tag_id: INTEGER
object_id: INTEGER
object_type: VARCHAR(9)
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "AnnotationLayer (annotation_layer)" as annotation_layer {
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
name: VARCHAR(250)
descr: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "DynamicPlugin (dynamic_plugin)" as dynamic_plugin {
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
name: TEXT
key: TEXT
bundle_url: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "Tag (tag)" as tag {
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
name: VARCHAR(250)
type: VARCHAR(12)
description: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "CssTemplate (css_templates)" as css_templates {
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
template_name: VARCHAR(250)
css: TEXT
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "UserAttribute (user_attribute)" as user_attribute {
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
user_id: INTEGER
welcome_dashboard_id: INTEGER
avatar_url: VARCHAR(100)
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
}
rectangle "Inherited from Flask App Builder (FAB)" #black {
entity "ViewMenu (ab_view_menu)" as ab_view_menu {
id: INTEGER
name: VARCHAR(250)
}
entity "Permission (ab_permission)" as ab_permission {
id: INTEGER
name: VARCHAR(100)
}
entity "User (ab_user)" as ab_user {
id: INTEGER
first_name: VARCHAR(64)
last_name: VARCHAR(64)
username: VARCHAR(64)
password: VARCHAR(256)
active: BOOLEAN
email: VARCHAR(320)
last_login: DATETIME
login_count: INTEGER
fail_login_count: INTEGER
created_on: DATETIME
changed_on: DATETIME
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "RegisterUser (ab_register_user)" as ab_register_user {
id: INTEGER
first_name: VARCHAR(64)
last_name: VARCHAR(64)
username: VARCHAR(64)
password: VARCHAR(256)
email: VARCHAR(64)
registration_date: DATETIME
registration_hash: VARCHAR(256)
}
entity "PermissionView (ab_permission_view)" as ab_permission_view {
id: INTEGER
permission_id: INTEGER
view_menu_id: INTEGER
}
entity "Role (ab_role)" as ab_role {
id: INTEGER
name: VARCHAR(64)
}
}
rectangle "Alerts & Reports" #black {
entity "ReportRecipients (report_recipient)" as report_recipient {
created_on: DATETIME
changed_on: DATETIME
id: INTEGER
type: VARCHAR(50)
recipient_config_json: TEXT
report_schedule_id: INTEGER
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
entity "ReportExecutionLog (report_execution_log)" as report_execution_log {
id: INTEGER
uuid: BINARY(16)
scheduled_dttm: DATETIME
start_dttm: DATETIME
end_dttm: DATETIME
value: FLOAT
value_row_json: TEXT
state: VARCHAR(50)
error_message: TEXT
report_schedule_id: INTEGER
}
entity "ReportSchedule (report_schedule)" as report_schedule {
created_on: DATETIME
changed_on: DATETIME
extra_json: TEXT
id: INTEGER
type: VARCHAR(50)
name: VARCHAR(150)
description: TEXT
context_markdown: TEXT
active: BOOLEAN
crontab: VARCHAR(1000)
creation_method: VARCHAR(255)
timezone: VARCHAR(100)
report_format: VARCHAR(50)
sql: TEXT
chart_id: INTEGER
dashboard_id: INTEGER
database_id: INTEGER
last_eval_dttm: DATETIME
last_state: VARCHAR(50)
last_value: FLOAT
last_value_row_json: TEXT
validator_type: VARCHAR(100)
validator_config_json: TEXT
log_retention: INTEGER
grace_period: INTEGER
working_timeout: INTEGER
force_screenshot: BOOLEAN
custom_width: INTEGER
custom_height: INTEGER
created_by_fk: INTEGER
changed_by_fk: INTEGER
}
}
' Relationships
sql_metrics }|--|| tables
sql_metrics }|--|| ab_user
database_user_oauth2_tokens }|--|| ab_user
database_user_oauth2_tokens }|--|| dbs
sl_tables }|--|| dbs
sl_tables }|--|{ sl_columns
sl_tables }|--|| ab_user
sl_tables }|--|{ sl_datasets
dbs }|--|| ab_user
dbs ||--|{ tables
dbs ||--|{ sl_datasets
sl_datasets }|--|{ sl_columns
sl_datasets }|--|{ ab_user
tables ||--|{ table_columns
tables }|--|{ row_level_security_filters
table_columns }|--|| ab_user
sl_columns }|--|| ab_user
row_level_security_filters }|--|| ab_user
key_value }|--|| ab_user
ssh_tunnels }|--|| dbs
ssh_tunnels }|--|| ab_user
saved_query }|--|| ab_user
saved_query }|--|| dbs
saved_query }|--|{ tag
table_schema }|--|| dbs
table_schema }|--|| ab_user
table_schema }|--|| tab_state
query }|--|| dbs
query }|--|| ab_user
tab_state }|--|| dbs
tab_state }|--|| query
tab_state }|--|| saved_query
tab_state }|--|| ab_user
dashboards }|--|{ slices
dashboards }|--|{ ab_user
dashboards }|--|{ tag
dashboards }|--|{ ab_role
dashboards ||--|{ embedded_dashboards
dashboards ||--|{ report_schedule
annotation }|--|| annotation_layer
annotation }|--|| ab_user
embedded_dashboards }|--|| ab_user
slices }|--|| ab_user
slices }|--|{ tag
slices }|--|| tables
slices ||--|{ report_schedule
tagged_object }|--|| tag
tagged_object }|--|| ab_user
annotation_layer }|--|| ab_user
dynamic_plugin }|--|| ab_user
tag }|--|{ ab_user
css_templates }|--|| ab_user
user_attribute }|--|| dashboards
ab_user }|--|{ ab_role
ab_user }|--|| ab_user
ab_user ||--|{ logs
ab_user ||--|{ user_attribute
ab_user }|--|{ tables
ab_permission_view }|--|| ab_permission
ab_permission_view }|--|| ab_view_menu
ab_permission_view }|--|{ ab_role
ab_role }|--|{ row_level_security_filters
report_recipient }|--|| report_schedule
report_recipient }|--|| ab_user
report_execution_log }|--|| report_schedule
report_schedule }|--|| dbs
report_schedule }|--|{ ab_user
@enduml

211
scripts/erd/erd.py Normal file
View File

@ -0,0 +1,211 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""
This module contains utilities to auto-generate an
Entity-Relationship Diagram (ERD) from SQLAlchemy
and onto a plantuml file.
"""
import json
import os
from collections import defaultdict
from collections.abc import Iterable
from typing import Any, Optional
import click
import jinja2
from flask.cli import FlaskGroup, with_appcontext
from superset import app, db
GROUPINGS: dict[str, Iterable[str]] = {
"Core": [
"css_templates",
"dynamic_plugin",
"favstar",
"dashboards",
"slices",
"user_attribute",
"embedded_dashboards",
"annotation",
"annotation_layer",
"tag",
"tagged_object",
],
"System": ["ssh_tunnels", "keyvalue", "cache_keys", "key_value", "logs"],
"Alerts & Reports": ["report_recipient", "report_execution_log", "report_schedule"],
"Inherited from Flask App Builder (FAB)": [
"ab_user",
"ab_permission",
"ab_permission_view",
"ab_view_menu",
"ab_role",
"ab_register_user",
],
"SQL Lab": ["query", "saved_query", "tab_state", "table_schema"],
"Data Assets": [
"dbs",
"table_columns",
"sql_metrics",
"tables",
"row_level_security_filters",
"sl_tables",
"sl_datasets",
"sl_columns",
"database_user_oauth2_tokens",
],
}
# Table name to group name mapping (reversing the above one for easy lookup)
TABLE_TO_GROUP_MAP: dict[str, str] = {}
for group, tables in GROUPINGS.items():
for table in tables:
TABLE_TO_GROUP_MAP[table] = group
def sort_data_structure(data): # type: ignore
sorted_json = json.dumps(data, sort_keys=True)
sorted_data = json.loads(sorted_json)
return sorted_data
def introspect_sqla_model(mapper: Any, seen: set[str]) -> dict[str, Any]:
"""
Introspects a SQLAlchemy model and returns a data structure that
can be pass to a jinja2 template for instance
Parameters:
-----------
mapper: SQLAlchemy model mapper
seen: set of model identifiers to avoid duplicates
Returns:
--------
Dict[str, Any]: data structure for jinja2 template
"""
table_name = mapper.persist_selectable.name
model_info: dict[str, Any] = {
"class_name": mapper.class_.__name__,
"table_name": table_name,
"fields": [],
"relationships": [],
}
# Collect fields (columns) and their types
for column in mapper.columns:
field_info: dict[str, str] = {
"field_name": column.key,
"type": str(column.type),
}
model_info["fields"].append(field_info)
# Collect relationships and identify types
for attr, relationship in mapper.relationships.items():
related_table = relationship.mapper.persist_selectable.name
# Create a unique identifier for the relationship to avoid duplicates
relationship_id = "-".join(sorted([table_name, related_table]))
if relationship_id not in seen:
seen.add(relationship_id)
squiggle = "||--|{"
if relationship.direction.name == "MANYTOONE":
squiggle = "}|--||"
relationship_info: dict[str, str] = {
"relationship_name": attr,
"related_model": relationship.mapper.class_.__name__,
"type": relationship.direction.name,
"related_table": related_table,
}
# Identify many-to-many by checking for secondary table
if relationship.secondary is not None:
squiggle = "}|--|{"
relationship_info["type"] = "many-to-many"
relationship_info["secondary_table"] = relationship.secondary.name
relationship_info["squiggle"] = squiggle
model_info["relationships"].append(relationship_info)
return sort_data_structure(model_info) # type: ignore
def introspect_models() -> dict[str, list[dict[str, Any]]]:
"""
Introspects SQLAlchemy models and returns a data structure that
can be pass to a jinja2 template for rendering an ERD.
Returns:
--------
Dict[str, List[Dict[str, Any]]]: data structure for jinja2 template
"""
data: dict[str, list[dict[str, Any]]] = defaultdict(list)
seen_models: set[str] = set()
for model in db.Model.registry.mappers:
group_name = (
TABLE_TO_GROUP_MAP.get(model.mapper.persist_selectable.name)
or "Uncategorized Models"
)
model_data = introspect_sqla_model(model, seen_models)
data[group_name].append(model_data)
return data
def generate_erd(file_path: str) -> None:
"""
Generates a PlantUML ERD of the models/database
Parameters:
-----------
file_path: str
File path to write the ERD to
"""
data = introspect_models()
templates_path = os.path.dirname(__file__)
env = jinja2.Environment(loader=jinja2.FileSystemLoader(templates_path))
# Load the template
template = env.get_template("erd.template.puml")
rendered = template.render(data=data)
with open(file_path, "w") as f:
click.secho(f"Writing to {file_path}...", fg="green")
f.write(rendered)
@click.command()
@click.option(
"--output",
"-o",
type=click.Path(dir_okay=False, writable=True),
help="File to write the ERD to",
)
def erd(output: Optional[str] = None) -> None:
"""
Generates a PlantUML ERD of the models/database
Parameters:
-----------
output: str, optional
File to write the ERD to, defaults to erd.plantuml if not provided
"""
path = os.path.dirname(__file__)
output = output or os.path.join(path, "erd.puml")
from superset.app import create_app
app = create_app()
with app.app_context():
generate_erd(output)
if __name__ == "__main__":
erd()

0
scripts/erd/erd.svg Normal file
View File

View File

@ -0,0 +1,57 @@
{#
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
#}
@startuml erd
title Apache Superset ERD
!theme blueprint
' avoid problems with angled crows feet
skinparam linetype ortho
skinparam classBorderColor #grey
skinparam classBorderColor<<new>> #white
skinparam classBorderThickness<<new>> 1
skinparam classLineStyle<<new>> Dashed
skinparam ClassBackgroundColor<<new>> #204143
' Models
{% for group_name, models in data.items() -%}
rectangle "{{ group_name }}" #black {
{% for model in models -%}
entity "{{ model.class_name }} ({{ model.table_name }})" as {{ model.table_name }} {
{%- for field in model.fields %}
{{ field.field_name }}: {{ field.type -}}
{%- endfor %}
}
{% endfor -%}
}
{% endfor -%}
' Relationships
{% for models in data.values() -%}
{% for model in models -%}
{%- for rel in model.relationships %}
{{ model.table_name }} {{ rel.squiggle }} {{ rel.related_table }}
{%- endfor %}
{% endfor -%}
{% endfor -%}
@enduml

View File

@ -0,0 +1,57 @@
{#
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
#}
@startuml entity-relationship-diagram
title Apache Superset ERD
!theme blueprint
' avoid problems with angled crows feet
skinparam linetype ortho
skinparam classBorderColor #grey
skinparam classBorderColor<<new>> #white
skinparam classBorderThickness<<new>> 1
skinparam classLineStyle<<new>> Dashed
skinparam ClassBackgroundColor<<new>> #204143
' Models
{% for group_name, models in data.items() -%}
rectangle "{{ group_name }}" #black {
{% for model in models -%}
entity "{{ model.class_name }} ({{ model.table_name }})" as {{ model.table_name }} {
{%- for field in model.fields %}
{{ field.field_name }}: {{ field.type -}}
{%- endfor %}
}
{% endfor -%}
}
{% endfor -%}
' Relationships
{% for models in data.values() -%}
{% for model in models -%}
{%- for rel in model.relationships %}
{{ model.table_name }} {{ rel.squiggle }} {{ rel.related_table }}
{%- endfor %}
{% endfor -%}
{% endfor -%}
@enduml