-
Notifications
You must be signed in to change notification settings - Fork 7
feat: redesign Connections and Environments pages with rich tables and stepped Drawers #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
dc765e1
1b7d493
bb482f7
d376890
f6bd778
2274db7
f3e25aa
a0c5ec7
2c784dd
110abcf
a0344be
d91acc1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,28 @@ | |||||||||
| from backend.utils.pagination import CustomPaginator | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def _get_host_display(con_model): | ||||||||||
| """Extract a human-readable host string from decrypted connection details.""" | ||||||||||
| try: | ||||||||||
| details = con_model.decrypted_connection_details | ||||||||||
| ds = con_model.datasource_name | ||||||||||
| if ds in ("postgres", "mysql", "trino"): | ||||||||||
| host = details.get("host", "") | ||||||||||
| port = details.get("port", "") | ||||||||||
| return f"{host}:{port}" if host and port else host or None | ||||||||||
| if ds == "snowflake": | ||||||||||
| return details.get("account") or None | ||||||||||
| if ds == "bigquery": | ||||||||||
| return details.get("project_id") or None | ||||||||||
| if ds == "databricks": | ||||||||||
| return details.get("host") or None | ||||||||||
| if ds == "duckdb": | ||||||||||
| return details.get("file_path") or None | ||||||||||
| except Exception: | ||||||||||
| pass | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bare
Suggested change
Also worth a unit test with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in a0c5ec7 — added |
||||||||||
| return None | ||||||||||
|
|
||||||||||
|
|
||||||||||
| class ConnectionSession: | ||||||||||
|
|
||||||||||
| @staticmethod | ||||||||||
|
|
@@ -58,20 +80,28 @@ def get_all_connections(page: int, limit: int, filter_condition: dict[str, Any]) | |||||||||
| is_sample_project = project_con.is_sample | ||||||||||
| else: | ||||||||||
| is_sample_project = False | ||||||||||
| env_count = EnvironmentModels.objects.filter( | ||||||||||
| connection_model_id=con_model.connection_id, is_deleted=False | ||||||||||
| ).count() | ||||||||||
| project_count = ProjectDetails.objects.filter( | ||||||||||
| connection_model_id=con_model.connection_id | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Must Fix: N+1 queries — 3 extra DB queries per connection row This loop fires Fix with from django.db.models import Count, Q
con_models_qs = ConnectionDetails.objects.filter(
**filter_condition, is_deleted=False
).annotate(
env_count=Count(
'environmentmodels',
filter=Q(environmentmodels__is_deleted=False)
),
project_count=Count('projectdetails')
).order_by('-modified_at')Then access
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in f3e25aa — replaced the per-row loop queries with a single |
||||||||||
| ).count() | ||||||||||
| connection_list.append( | ||||||||||
| { | ||||||||||
| "id": con_model.connection_id, | ||||||||||
|
greptile-apps[bot] marked this conversation as resolved.
Outdated
|
||||||||||
| "name": con_model.connection_name, | ||||||||||
| "description": con_model.connection_description, | ||||||||||
| "datasource_name": con_model.datasource_name, | ||||||||||
| "host": _get_host_display(con_model), | ||||||||||
| "created_by": con_model.created_by, | ||||||||||
| "last_modified_by": con_model.last_modified_by, | ||||||||||
| "db_icon": import_file(f"visitran.adapters.{con_model.datasource_name}").ICON, | ||||||||||
| "is_connection_exist": con_model.is_connection_exist, | ||||||||||
| "is_connection_valid": con_model.is_connection_valid, | ||||||||||
| "connection_flag": con_model.connection_flag, | ||||||||||
| "is_sample_project": is_sample_project, | ||||||||||
| # "connection_details": con_model.connection_details, # skipping connection_details | ||||||||||
| "env_count": env_count, | ||||||||||
| "project_count": project_count, | ||||||||||
| } | ||||||||||
| ) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,11 @@ | |
|
|
||
| from visitran.utils import import_file | ||
|
|
||
| from backend.application.session.connection_session import ConnectionSession | ||
| from backend.application.session.connection_session import ConnectionSession, _get_host_display | ||
| from backend.application.utils import get_filter | ||
| from backend.core.models.environment_models import EnvironmentModels | ||
| from backend.core.models.project_details import ProjectDetails | ||
| from backend.core.scheduler.models import UserTaskDetails | ||
| from backend.errors.exceptions import EnvironmentAlreadyExist, EnvironmentNotExists | ||
| from backend.utils.pagination import CustomPaginator | ||
|
|
||
|
|
@@ -89,6 +90,12 @@ def get_all_environments(self, page: int, limit: int, filter_condition: dict[str | |
|
|
||
| env_data = [] | ||
| for env_model in env_models.get("page_items"): | ||
| job_count = UserTaskDetails.objects.filter( | ||
| environment=env_model | ||
| ).count() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Must Fix: Same N+1 pattern — 2 count queries per environment row + missing
Fix: env_qs = EnvironmentModels.objects.filter(
**filter_condition, is_deleted=False
).select_related(
'connection_model' # eliminates ForeignKey N+1
).annotate(
job_count=Count('usertaskdetails'),
project_count=Count('projectdetails')
).order_by('-modified_at')
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in f3e25aa — added |
||
| project_count = ProjectDetails.objects.filter( | ||
| environment_model=env_model | ||
| ).count() | ||
| env_data.append( | ||
| { | ||
| "id": env_model.environment_id, | ||
|
|
@@ -100,8 +107,12 @@ def get_all_environments(self, page: int, limit: int, filter_condition: dict[str | |
| "name": env_model.connection_model.connection_name, | ||
| "datasource_name": env_model.connection_model.datasource_name, | ||
| "db_icon": import_file(f"visitran.adapters.{env_model.connection_model.datasource_name}").ICON, | ||
| "host": _get_host_display(env_model.connection_model), | ||
| "connection_flag": env_model.connection_model.connection_flag, | ||
| }, | ||
| "is_tested": env_model.is_tested, | ||
| "job_count": job_count, | ||
| "project_count": project_count, | ||
| } | ||
| ) | ||
| env_models["page_items"] = env_data | ||
|
|
@@ -118,6 +129,9 @@ def get_environment(self, environment_id: str) -> dict[str, Any]: | |
| "id": env_model.connection_model.connection_id, | ||
| "name": env_model.connection_model.connection_name, | ||
| "datasource_name": env_model.connection_model.datasource_name, | ||
| "db_icon": import_file(f"visitran.adapters.{env_model.connection_model.datasource_name}").ICON, | ||
| "host": _get_host_display(env_model.connection_model), | ||
| "connection_flag": env_model.connection_model.connection_flag, | ||
| }, | ||
| "connection_details": env_model.masked_connection_data, | ||
| "custom_data": env_model.env_custom_data, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must Fix:
_get_host_display()decrypts passwords/tokens unnecessarilycon_model.decrypted_connection_detailsruns Fernet decryption on every sensitive field (password, api_key, token, connection_url, etc.) — but this function only reads non-sensitive fields likehost,port,account,project_id.These fields are stored as plaintext in the DB (only fields in
SENSITIVE_FIELDSare encrypted). So you can readconnection_detailsdirectly and skip decryption entirely:This eliminates Fernet CPU overhead on every row in the list API (20 connections × decryption = noticeable on page load).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in f3e25aa — now reads
connection_detailsdirectly (the raw JSON) instead ofdecrypted_connection_details. Host, port, account, project_id are all plaintext — no Fernet decryption needed.