Skip to content

Commit a31afa7

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents 2ccfa39 + 3f3bbe3 commit a31afa7

5 files changed

Lines changed: 75 additions & 49 deletions

File tree

ddpui/api/report_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ def create_snapshot(request, payload: SnapshotCreate):
7777
s = ReportService.create_snapshot(
7878
title=payload.title,
7979
dashboard_id=payload.dashboard_id,
80-
date_column=payload.date_column.model_dump(),
81-
period_end=payload.period_end,
8280
orguser=orguser,
81+
date_column=payload.date_column.model_dump() if payload.date_column else {},
82+
period_end=payload.period_end,
8383
period_start=payload.period_start,
8484
)
8585
return api_response(

ddpui/core/reports/report_service.py

Lines changed: 44 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,9 @@ def get_report_chart_data(
299299
def create_snapshot(
300300
title: str,
301301
dashboard_id: int,
302-
date_column: Dict[str, str],
303-
period_end: date,
304302
orguser: OrgUser,
303+
date_column: Optional[Dict[str, str]] = None,
304+
period_end: Optional[date] = None,
305305
period_start: Optional[date] = None,
306306
) -> ReportSnapshot:
307307
"""Create a snapshot from a dashboard.
@@ -314,10 +314,11 @@ def create_snapshot(
314314
Args:
315315
title: User-provided title for the snapshot
316316
dashboard_id: ID of the dashboard to snapshot
317-
date_column: Dictionary with {schema_name, table_name, column_name}
318-
identifying the datetime column for period filtering
319-
period_end: End of reporting period (inclusive)
320317
orguser: The user creating the snapshot
318+
date_column: Dictionary with {schema_name, table_name, column_name}
319+
identifying the datetime column for period filtering.
320+
None for dashboards without datetime columns.
321+
period_end: End of reporting period (inclusive). None when no date filtering.
321322
period_start: Start of reporting period (inclusive). None means no lower bound.
322323
323324
Returns:
@@ -328,7 +329,7 @@ def create_snapshot(
328329
dashboard doesn't exist, or date_column is
329330
not a valid datetime column in the warehouse
330331
"""
331-
if period_start is not None and period_start > period_end:
332+
if period_start is not None and period_end is not None and period_start > period_end:
332333
raise SnapshotValidationError("period_start must be before period_end")
333334

334335
# Fetch dashboard with filters prefetched (used only for freezing)
@@ -339,45 +340,45 @@ def create_snapshot(
339340
except Dashboard.DoesNotExist:
340341
raise SnapshotValidationError(f"Dashboard {dashboard_id} not found")
341342

342-
# Validate date_column: first check dashboard filters, then fall back
343-
# to verifying the column exists in the warehouse as a datetime type
344-
datetime_filters = dashboard.filters.filter(filter_type="datetime")
345-
match_on_filter = datetime_filters.filter(
346-
schema_name=date_column["schema_name"],
347-
table_name=date_column["table_name"],
348-
column_name=date_column["column_name"],
349-
).exists()
350-
351-
if not match_on_filter:
352-
# Fallback: verify the column exists in the warehouse as datetime
353-
org_warehouse = OrgWarehouse.objects.filter(org=orguser.org).first()
354-
if not org_warehouse:
355-
raise SnapshotValidationError("Warehouse not configured")
356-
357-
warehouse_client = WarehouseFactory.get_warehouse_client(org_warehouse)
358-
all_columns = warehouse_client.get_table_columns(
359-
date_column["schema_name"],
360-
date_column["table_name"],
361-
)
362-
363-
# Find the specific column
364-
target_col = None
365-
for col in all_columns:
366-
if col["name"] == date_column["column_name"]:
367-
target_col = col
368-
break
369-
370-
if not target_col:
371-
raise SnapshotValidationError(
372-
f"Column '{date_column['column_name']}' not found in "
373-
f"{date_column['schema_name']}.{date_column['table_name']}"
343+
# Validate date_column if provided
344+
if date_column:
345+
datetime_filters = dashboard.filters.filter(filter_type="datetime")
346+
match_on_filter = datetime_filters.filter(
347+
schema_name=date_column["schema_name"],
348+
table_name=date_column["table_name"],
349+
column_name=date_column["column_name"],
350+
).exists()
351+
352+
if not match_on_filter:
353+
# Fallback: verify the column exists in the warehouse as datetime
354+
org_warehouse = OrgWarehouse.objects.filter(org=orguser.org).first()
355+
if not org_warehouse:
356+
raise SnapshotValidationError("Warehouse not configured")
357+
358+
warehouse_client = WarehouseFactory.get_warehouse_client(org_warehouse)
359+
all_columns = warehouse_client.get_table_columns(
360+
date_column["schema_name"],
361+
date_column["table_name"],
374362
)
375363

376-
if target_col.get("translated_type") != TranslateColDataType.DATETIME:
377-
raise SnapshotValidationError(
378-
f"Column '{date_column['column_name']}' is not a datetime column "
379-
f"(type: {target_col['data_type']})"
380-
)
364+
# Find the specific column
365+
target_col = None
366+
for col in all_columns:
367+
if col["name"] == date_column["column_name"]:
368+
target_col = col
369+
break
370+
371+
if not target_col:
372+
raise SnapshotValidationError(
373+
f"Column '{date_column['column_name']}' not found in "
374+
f"{date_column['schema_name']}.{date_column['table_name']}"
375+
)
376+
377+
if target_col.get("translated_type") != TranslateColDataType.DATETIME:
378+
raise SnapshotValidationError(
379+
f"Column '{date_column['column_name']}' is not a datetime column "
380+
f"(type: {target_col['data_type']})"
381+
)
381382

382383
frozen_dashboard = FrozenDashboardConfig(
383384
**ReportService._freeze_dashboard(dashboard)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 4.2 on 2026-04-28 09:17
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("ddpui", "0157_orgdbt_is_repo_managed_by_system"),
9+
]
10+
11+
operations = [
12+
migrations.AlterField(
13+
model_name="reportsnapshot",
14+
name="period_end",
15+
field=models.DateField(
16+
blank=True,
17+
help_text="End of reporting period (inclusive). NULL for snapshots without date filtering.",
18+
null=True,
19+
),
20+
),
21+
]

ddpui/models/report.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ class ReportSnapshot(models.Model):
2929
blank=True,
3030
help_text="Start of reporting period (inclusive). NULL = no lower bound.",
3131
)
32-
period_end = models.DateField(help_text="End of reporting period (inclusive)")
32+
period_end = models.DateField(
33+
null=True,
34+
blank=True,
35+
help_text="End of reporting period (inclusive). NULL for snapshots without date filtering.",
36+
)
3337

3438
# --- FROZEN DATA (2 layers) ---
3539

ddpui/schemas/report_schema.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ class SnapshotCreate(Schema):
5353

5454
title: str = Field(..., min_length=1, max_length=255)
5555
dashboard_id: int
56-
date_column: DateColumnSchema
56+
date_column: Optional[DateColumnSchema] = None
5757
period_start: Optional[date] = None # None = no lower bound
58-
period_end: date
58+
period_end: Optional[date] = None
5959

6060

6161
class SnapshotUpdate(Schema):
@@ -75,7 +75,7 @@ class SnapshotResponse(Schema):
7575
dashboard_title: Optional[str] = None # From frozen_dashboard, not a live FK
7676
date_column: Optional[Dict[str, str]] = None
7777
period_start: Optional[date] = None
78-
period_end: date
78+
period_end: Optional[date] = None
7979
summary: Optional[str] = None
8080
created_by: Optional[str] = None
8181
created_at: datetime

0 commit comments

Comments
 (0)