Skip to content

Commit 7946018

Browse files
committed
fix: Fixed monitoring page issues
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
1 parent ba2c05c commit 7946018

5 files changed

Lines changed: 120 additions & 4 deletions

File tree

.cursor/rules/feast-ui.mdc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
description: Formatting and lint rules for the Feast UI (React/TypeScript)
3+
globs: ui/src/**
4+
alwaysApply: false
5+
---
6+
7+
## After editing any file under `ui/src/`
8+
9+
1. **Run Prettier** before considering the task complete:
10+
```bash
11+
cd ui && yarn prettier --write <changed-files>
12+
```
13+
2. **Verify** formatting passes:
14+
```bash
15+
cd ui && yarn format:check
16+
```
17+
CI runs `yarn format:check` and will reject PRs with style violations.
18+
19+
3. Prettier config lives in `ui/package.json` (no separate `.prettierrc`). Do not override it.

sdk/python/feast/api/registry/rest/monitoring.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ def _get_monitoring_service():
6464
status_code=503,
6565
detail="Monitoring service is not available: no FeatureStore configured",
6666
)
67+
dqm_config = getattr(store.config, "data_quality_monitoring_config", None)
68+
if dqm_config is None:
69+
raise HTTPException(
70+
status_code=503,
71+
detail="Monitoring is not enabled: add data_quality_monitoring section to feature_store.yaml",
72+
)
6773
from feast.monitoring.monitoring_service import MonitoringService
6874

6975
_monitoring_service = MonitoringService(store)

sdk/python/feast/monitoring/monitoring_service.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ def auto_compute(
9292
project: Optional[str] = None,
9393
feature_view_name: Optional[str] = None,
9494
) -> Dict[str, Any]:
95-
"""Detect date ranges from source data and compute all granularities."""
95+
"""Detect date ranges from source data and compute all granularities.
96+
97+
Also computes baseline for any features that don't have one yet
98+
(idempotent -- features with existing baselines are skipped).
99+
"""
96100
start_time = time.time()
97101
self._ensure_monitoring_tables()
98102
if project is None:
@@ -101,6 +105,7 @@ def auto_compute(
101105
feature_views = self._resolve_feature_views(project, feature_view_name)
102106
total_features = 0
103107
total_views = 0
108+
baseline_features = 0
104109
granularities_computed = set()
105110

106111
for fv in feature_views:
@@ -118,6 +123,32 @@ def auto_compute(
118123

119124
now = datetime.now(timezone.utc)
120125

126+
# Compute baseline for features that don't have one yet
127+
fields_needing_baseline = self._get_features_without_baseline(
128+
project, fv
129+
)
130+
if fields_needing_baseline:
131+
bl_fields = self._classify_fields(
132+
fv, fields=fields_needing_baseline
133+
)
134+
if bl_fields:
135+
bl_metrics = self._compute_feature_metrics(
136+
fv,
137+
bl_fields,
138+
_EPOCH,
139+
_FAR_FUTURE,
140+
)
141+
self._save_computed_metrics(
142+
project=project,
143+
feature_view=fv,
144+
metrics_list=bl_metrics,
145+
metric_date=date.today(),
146+
granularity="baseline",
147+
set_baseline=True,
148+
now=now,
149+
)
150+
baseline_features += len(bl_metrics)
151+
121152
for granularity, window in GRANULARITY_WINDOWS.items():
122153
window_start = max_ts - window
123154
metrics_list = self._compute_feature_metrics(
@@ -155,6 +186,7 @@ def auto_compute(
155186
"status": "completed",
156187
"computed_feature_views": total_views,
157188
"computed_features": total_features,
189+
"baseline_features": baseline_features,
158190
"granularities": sorted(granularities_computed),
159191
"duration_ms": duration_ms,
160192
}

ui/src/pages/monitoring/Index.tsx

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useDocumentTitle } from "../../hooks/useDocumentTitle";
1414
import useLoadRegistry from "../../queries/useLoadRegistry";
1515
import RegistryPathContext from "../../contexts/RegistryPathContext";
1616
import {
17+
isServiceUnavailable,
1718
useFeatureMetrics,
1819
useFeatureViewMetrics,
1920
useFeatureServiceMetrics,
@@ -33,7 +34,7 @@ const MonitoringIndex = () => {
3334
const { data: registryData } = useLoadRegistry(registryUrl, projectName);
3435

3536
const [selectedFV, setSelectedFV] = useState("");
36-
const [granularity, setGranularity] = useState("baseline");
37+
const [granularity, setGranularity] = useState("daily");
3738
const [dataSourceType, setDataSourceType] = useState("");
3839
const [startDate, setStartDate] = useState("");
3940
const [endDate, setEndDate] = useState("");
@@ -112,7 +113,13 @@ const MonitoringIndex = () => {
112113
});
113114
};
114115

115-
const hasError = featureQuery.isError && fvQuery.isError && fsQuery.isError;
116+
const allFailed = featureQuery.isError && fvQuery.isError && fsQuery.isError;
117+
const monitoringNotEnabled =
118+
allFailed &&
119+
isServiceUnavailable(featureQuery.error) &&
120+
isServiceUnavailable(fvQuery.error) &&
121+
isServiceUnavailable(fsQuery.error);
122+
const hasError = allFailed && !monitoringNotEnabled;
116123
const hasData =
117124
(featureQuery.data && featureQuery.data.length > 0) ||
118125
(fvQuery.data && fvQuery.data.length > 0);
@@ -162,6 +169,43 @@ const MonitoringIndex = () => {
162169
},
163170
];
164171

172+
if (monitoringNotEnabled) {
173+
return (
174+
<EuiPageTemplate panelled>
175+
<EuiPageTemplate.Header
176+
restrictWidth
177+
iconType="monitoringApp"
178+
pageTitle="Monitoring"
179+
description="Feature quality and data health metrics for your feature store."
180+
/>
181+
<EuiPageTemplate.Section>
182+
<EuiEmptyPrompt
183+
iconType="alert"
184+
color="subdued"
185+
title={<h2>Monitoring Is Not Enabled</h2>}
186+
body={
187+
<>
188+
<p>
189+
Data quality monitoring is not configured for this Feast
190+
deployment.
191+
</p>
192+
<p>
193+
To enable monitoring, add the following to your{" "}
194+
<code>feature_store.yaml</code>:
195+
</p>
196+
<pre style={{ textAlign: "left", display: "inline-block" }}>
197+
{`data_quality_monitoring:
198+
auto_baseline: true`}
199+
</pre>
200+
<p>Then restart the Feast registry server.</p>
201+
</>
202+
}
203+
/>
204+
</EuiPageTemplate.Section>
205+
</EuiPageTemplate>
206+
);
207+
}
208+
165209
return (
166210
<EuiPageTemplate panelled>
167211
<EuiPageTemplate.Header

ui/src/queries/useMonitoringApi.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ const buildQueryString = (params: Record<string, string | undefined>) => {
108108
);
109109
};
110110

111+
class MonitoringApiError extends Error {
112+
status: number;
113+
constructor(status: number, message: string) {
114+
super(message);
115+
this.status = status;
116+
}
117+
}
118+
111119
const fetchMonitoring = async <T>(
112120
baseUrl: string,
113121
path: string,
@@ -124,7 +132,10 @@ const fetchMonitoring = async <T>(
124132
credentials: fetchOptions?.credentials,
125133
});
126134
if (!res.ok) {
127-
throw new Error(`Failed to fetch ${path}: ${res.status} ${res.statusText}`);
135+
throw new MonitoringApiError(
136+
res.status,
137+
`Failed to fetch ${path}: ${res.status} ${res.statusText}`,
138+
);
128139
}
129140
const text = await res.text();
130141
const sanitized = text
@@ -134,6 +145,9 @@ const fetchMonitoring = async <T>(
134145
return JSON.parse(sanitized);
135146
};
136147

148+
const isServiceUnavailable = (error: unknown): boolean =>
149+
error instanceof MonitoringApiError && error.status === 503;
150+
137151
const STALE_TIME = 30_000;
138152

139153
const useFeatureMetrics = (filters: MonitoringFilters) => {
@@ -287,6 +301,7 @@ const useComputeMetrics = () => {
287301
};
288302

289303
export {
304+
isServiceUnavailable,
290305
useFeatureMetrics,
291306
useFeatureViewMetrics,
292307
useFeatureServiceMetrics,

0 commit comments

Comments
 (0)