Skip to content

Commit 5c59062

Browse files
committed
add lastN string functionality
1 parent b2619f9 commit 5c59062

File tree

5 files changed

+113
-30
lines changed

5 files changed

+113
-30
lines changed

dashboard/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,4 @@
289289

290290
app.state = AppState()
291291

292-
serve()
292+
serve(port="5003")

dashboard/components.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,18 +207,17 @@ def _create_last_n_form(batch_name):
207207
"""
208208
Create the last n number form for the dashboard.
209209
"""
210-
state = get_state() # Get state instance first
210+
state = get_state()
211211
return Form(
212212
DivLAligned(
213213
Input(
214-
type="number",
214+
type="text",
215215
name="last_n",
216-
value=state.last_n.get(batch_name, DEFAULT_LAST_N),
217-
min=1,
218-
max=1000,
219-
step=1,
216+
value=state.last_n.get(batch_name, "30n"),
217+
pattern="^\d+[nNhmd]$",
218+
title="Use format: 30n (observations), 24h (hours), 45m (minutes), 7d (days)",
220219
cls="uk-input uk-form-small uk-form-width-small",
221-
uk_tooltip="Filter for last N observations",
220+
uk_tooltip="Filter by last N observations or time period (e.g., 30n, 24h, 45m, 7d)",
222221
),
223222
cls="space-x-2",
224223
),

dashboard/data.py

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,89 @@
55
import pandas as pd
66
from anomstack.jinja.render import render
77
from anomstack.sql.read import read_sql
8+
import re
9+
from datetime import datetime, timedelta
810

911

10-
def get_data(spec: dict, last_n: int = 30, ensure_timestamp: bool = False) -> pd.DataFrame:
12+
def parse_time_spec(spec_str: str) -> dict:
1113
"""
12-
Get data from the database for a given spec and last_n.
14+
Parse a time specification string into a dictionary with type and value.
15+
Supports formats:
16+
- "30N" or "30n" for last N observations
17+
- "24h" for last 24 hours
18+
- "45m" for last 45 minutes
19+
- "7d" for last 7 days
20+
21+
Returns:
22+
dict with keys:
23+
- 'type': 'n' for observations, 'time' for time-based
24+
- 'value': number of observations or timedelta object
25+
"""
26+
if not spec_str:
27+
return {'type': 'n', 'value': DEFAULT_LAST_N}
28+
29+
# Convert to string if number passed
30+
spec_str = str(spec_str).strip().lower()
31+
32+
# Match patterns
33+
n_pattern = re.match(r'^(\d+)n$', spec_str)
34+
time_pattern = re.match(r'^(\d+)([hmd])$', spec_str)
35+
36+
if n_pattern:
37+
return {'type': 'n', 'value': int(n_pattern.group(1))}
38+
39+
if time_pattern:
40+
value = int(time_pattern.group(1))
41+
unit = time_pattern.group(2)
42+
43+
delta = {
44+
'm': timedelta(minutes=value),
45+
'h': timedelta(hours=value),
46+
'd': timedelta(days=value)
47+
}[unit]
48+
49+
return {'type': 'time', 'value': delta}
50+
51+
# Try to parse as plain number for backward compatibility
52+
try:
53+
return {'type': 'n', 'value': int(spec_str)}
54+
except ValueError:
55+
raise ValueError(
56+
f"Invalid time specification: {spec_str}. "
57+
"Use format: 30n (observations), 24h (hours), 45m (minutes), 7d (days)"
58+
)
59+
60+
def get_data(spec: dict, last_n: str = "30n", ensure_timestamp: bool = False) -> pd.DataFrame:
61+
"""
62+
Get data from the database for a given spec and time specification.
1363
1464
Args:
1565
spec: The spec to get data for.
16-
last_n: The maximum number of observations to return.
66+
last_n: Time specification (e.g., "30n", "24h", "45m", "7d")
67+
ensure_timestamp: Whether to ensure timestamp column exists
1768
1869
Returns:
1970
A pandas DataFrame containing the data.
2071
"""
21-
sql = render(
22-
"dashboard_sql",
23-
spec,
24-
params={"last_n": last_n},
25-
)
72+
time_spec = parse_time_spec(last_n)
73+
74+
if time_spec['type'] == 'time':
75+
# For time-based queries, we'll need to modify the SQL
76+
cutoff_time = datetime.now() - time_spec['value']
77+
sql = render(
78+
"dashboard_sql",
79+
spec,
80+
params={"cutoff_time": cutoff_time.isoformat()},
81+
template_type="time_based" # You'll need to add this template
82+
)
83+
else:
84+
# For N-based queries, use existing logic
85+
sql = render(
86+
"dashboard_sql",
87+
spec,
88+
params={"last_n": time_spec['value']},
89+
)
90+
2691
db = spec["db"]
2792
df = read_sql(sql, db=db)
2893

dashboard/routes.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -395,24 +395,34 @@ def get(batch_name: str, search: str = ""):
395395

396396

397397
@rt("/batch/{batch_name}/update-n")
398-
def post(batch_name: str, last_n: int = DEFAULT_LAST_N, session=None):
398+
def post(batch_name: str, last_n: str = "30n", session=None):
399399
"""
400-
Update the number of observations for a given batch name.
400+
Update the time window for a given batch name.
401+
Supports formats like "30n" (observations), "24h" (hours), "45m" (minutes), "7d" (days)
401402
"""
402-
app.state.last_n[batch_name] = last_n
403-
app.state.clear_batch_cache(batch_name)
403+
try:
404+
# Store the raw specification
405+
app.state.last_n[batch_name] = last_n
406+
app.state.clear_batch_cache(batch_name)
404407

405-
app.state.df_cache[batch_name] = get_data(
406-
app.state.specs_enabled[batch_name],
407-
last_n=last_n,
408-
ensure_timestamp=True
409-
)
410-
app.state.calculate_metric_stats(batch_name)
408+
# Get new data with parsed specification
409+
app.state.df_cache[batch_name] = get_data(
410+
app.state.specs_enabled[batch_name],
411+
last_n=last_n,
412+
ensure_timestamp=True
413+
)
414+
app.state.calculate_metric_stats(batch_name)
411415

412-
# Return the full page content with proper URL update
413-
return get_batch_view(
414-
batch_name, session=session, initial_load=DEFAULT_LOAD_N_CHARTS
415-
)
416+
# Return the full page content with proper URL update
417+
return get_batch_view(
418+
batch_name, session=session, initial_load=DEFAULT_LOAD_N_CHARTS
419+
)
420+
except ValueError as e:
421+
# Return error message if invalid format
422+
return Div(
423+
P(str(e), cls="text-red-500 p-4 text-center"),
424+
id="main-content",
425+
)
416426

417427

418428
@rt("/batch/{batch_name}/toggle-size")

metrics/defaults/sql/dashboard.sql

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ where
2121
metric_type = 'metric'
2222
and
2323
metric_timestamp >= current_date - interval '{{ alert_metric_timestamp_max_days_ago }} day'
24+
{% if cutoff_time is defined %}
25+
and metric_timestamp >= '{{ cutoff_time }}'
26+
{% endif %}
2427
group by metric_timestamp, metric_batch, metric_name
2528
),
2629

@@ -176,8 +179,12 @@ select
176179
metric_change
177180
from
178181
data_smoothed
182+
{% if cutoff_time is defined %}
183+
/* When using time-based filtering, don't limit by recency rank */
184+
{% else %}
179185
where
180186
metric_value_recency_rank <= {{ last_n }}
187+
{% endif %}
181188
)
182189

183190
select
@@ -191,4 +198,6 @@ select
191198
metric_change
192199
from
193200
data_final
201+
order by
202+
metric_timestamp
194203
;

0 commit comments

Comments
 (0)