Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: Monitoring backend check and review comments fixed
Signed-off-by: Jitendra Yejare <11752425+jyejare@users.noreply.github.com>
  • Loading branch information
jyejare committed Jun 12, 2026
commit 3de7c9251dedeea8b530adce1de4885ff1adc31d
14 changes: 8 additions & 6 deletions docs/how-to-guides/feature-monitoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@ Done!
The baseline reads all available source data and stores the resulting statistics with `is_baseline=TRUE`. This serves as the reference distribution for future drift detection.

Baseline computation is:
- **Non-blocking** — `feast apply` returns immediately; computation runs asynchronously
- **Threaded** — runs in a background thread but completes before `feast apply` exits
- **Idempotent** — only features without existing baselines are computed; re-running `feast apply` won't recompute existing baselines

### Disabling auto-baseline
### Enabling auto-baseline

To skip automatic baseline computation on `feast apply`, set the DQM config in `feature_store.yaml`:
To enable automatic baseline computation on `feast apply`, set the DQM config in `feature_store.yaml`:

```yaml
DataQualityMonitoring:
auto_baseline: false
data_quality_monitoring:
auto_baseline: true
```

When using the Feast operator, set this in the `FeatureStore` CR:
Expand All @@ -63,9 +63,11 @@ kind: FeatureStore
spec:
feastProject: my_project
dataQualityMonitoring:
autoBaseline: false
autoBaseline: true
```

To disable it, set `auto_baseline: false` (or `autoBaseline: false` in the CR).

## 3. Scheduled monitoring with the CLI

### Auto mode (recommended for production)
Expand Down
23 changes: 23 additions & 0 deletions sdk/python/feast/api/registry/rest/monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,29 @@ def _get_store():
)
return store

@router.get("/monitoring/config", tags=["Monitoring"])
def monitoring_config():
"""Report whether DQM is configured, checking the live config file."""
import os

import yaml

s = _get_store()
dqm = getattr(s.config, "data_quality_monitoring_config", None)
if dqm is not None:
return {"enabled": True}

repo_path = getattr(s, "repo_path", None)
if repo_path:
cfg_file = os.path.join(str(repo_path), "feature_store.yaml")
if os.path.exists(cfg_file):
with open(cfg_file) as f:
cfg = yaml.safe_load(f)
if cfg and cfg.get("data_quality_monitoring"):
return {"enabled": True}

return {"enabled": False}

# ------------------------------------------------------------------ #
# DQM Job: submit and track
# ------------------------------------------------------------------ #
Expand Down
2 changes: 1 addition & 1 deletion sdk/python/feast/monitoring/monitoring_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ def compute_baseline(
feature_view=fv,
metrics_list=metrics_list,
metric_date=date.today(),
granularity="daily",
granularity="baseline",
set_baseline=True,
now=now,
)
Expand Down
2 changes: 1 addition & 1 deletion sdk/python/feast/ui_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def _setup_rest_mode(app: FastAPI, store: "feast.FeatureStore"):
grpc_handler = RegistryServer(store.registry)

rest_app = FastAPI(root_path="/api/v1")
register_all_routes(rest_app, grpc_handler)
register_all_routes(rest_app, grpc_handler, store=store)

class PushRequest(BaseModel):
push_source_name: str
Expand Down
33 changes: 23 additions & 10 deletions ui/src/FeastUISansProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect, useState } from "react";

import "./index.css";

Expand Down Expand Up @@ -109,6 +109,23 @@ const FeastUISansProvidersInner = ({
fetchOptions: feastUIConfigs?.fetchOptions,
};

const [autoMonitoringEnabled, setAutoMonitoringEnabled] = useState(false);
useEffect(() => {
if (feastUIConfigs?.monitoringConfig) return;
fetch("/api/v1/monitoring/config")
.then((r) => r.json())
.then((data) => {
if (data?.enabled) setAutoMonitoringEnabled(true);
})
.catch(() => {});
}, [feastUIConfigs?.monitoringConfig]);

const monitoringConfig: MonitoringConfig =
feastUIConfigs?.monitoringConfig || {
apiBaseUrl: "/api/v1",
enabled: autoMonitoringEnabled,
};

return (
<EuiProvider colorMode={colorMode}>
<EuiErrorBoundary>
Expand Down Expand Up @@ -144,14 +161,7 @@ const FeastUISansProvidersInner = ({
<FeatureFlagsContext.Provider
value={feastUIConfigs?.featureFlags || {}}
>
<MonitoringContext.Provider
value={
feastUIConfigs?.monitoringConfig || {
apiBaseUrl: "/api/v1",
enabled: true,
}
}
>
<MonitoringContext.Provider value={monitoringConfig}>
<ProjectListContext.Provider value={projectListContext}>
<Routes>
<Route path="/" element={<Layout />}>
Expand Down Expand Up @@ -195,7 +205,10 @@ const FeastUISansProvidersInner = ({
path="entity/:entityName/*"
element={<EntityInstance />}
/>
<Route path="label-view/" element={<LabelViewIndex />} />
<Route
path="label-view/"
element={<LabelViewIndex />}
/>
<Route
path="label-view/:labelViewName/*"
element={<LabelViewInstance />}
Expand Down
2 changes: 1 addition & 1 deletion ui/src/contexts/MonitoringContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface MonitoringConfig {

const MonitoringContext = React.createContext<MonitoringConfig>({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all monitoring hooks fire even when DQM isn’t configured. Consider defaulting to false and enabling only when DQM is present. Monitoring nav is always shown with no gating on monitoringConfig.enabled. Hide it when monitoring is disabled.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussions we are keeping it always and showing the empty state with requesting to enable monitoring, when monitoring is not enabled.

apiBaseUrl: "/api/v1",
enabled: true,
enabled: false,
});

export default MonitoringContext;
Expand Down
37 changes: 18 additions & 19 deletions ui/src/pages/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useState } from "react";
import React, { useContext, useState } from "react";

import { EuiIcon, EuiSideNav, htmlIdGenerator } from "@elastic/eui";
import { Link, useParams } from "react-router-dom";
import { useMatchSubpath } from "../hooks/useMatchSubpath";
import MonitoringContext from "../contexts/MonitoringContext";
import useResourceQuery, {
entityListPath,
featureViewListPath,
Expand Down Expand Up @@ -84,6 +85,8 @@ const SideNav = () => {
restSelect: restLabelViewsFromResponse,
});

const { enabled: monitoringEnabled } = useContext(MonitoringContext);

const [isSideNavOpenOnMobile, setisSideNavOpenOnMobile] = useState(false);

const toggleOpenOnMobile = () => {
Expand All @@ -99,6 +102,7 @@ const SideNav = () => {
const labelViewsLabel = `Label Views ${lvSuccess && labelViews && labelViews.length > 0 ? `(${labelViews.length})` : ""}`;

const baseUrl = `/p/${projectName}`;
const monitoringSelected = useMatchSubpath(`${baseUrl}/monitoring`);

const sideNav: React.ComponentProps<typeof EuiSideNav>["items"] = [
{
Expand Down Expand Up @@ -176,24 +180,19 @@ const SideNav = () => {
renderItem: (props) => <Link {...props} to={`${baseUrl}/data-set`} />,
isSelected: useMatchSubpath(`${baseUrl}/data-set`),
},
{
name: "Monitoring",
id: htmlIdGenerator("monitoring")(),
icon: <EuiIcon type="monitoringApp" />,
renderItem: (props) => (
<Link {...props} to={`${baseUrl}/monitoring`} />
),
isSelected: useMatchSubpath(`${baseUrl}/monitoring`),
},
{
name: "Data Labeling",
id: htmlIdGenerator("dataLabeling")(),
icon: <EuiIcon type="documentEdit" color="#006BB4" />,
renderItem: (props) => (
<Link {...props} to={`${baseUrl}/data-labeling`} />
),
isSelected: useMatchSubpath(`${baseUrl}/data-labeling`),
},
...(monitoringEnabled
? [
{
name: "Monitoring",
id: htmlIdGenerator("monitoring")(),
icon: <EuiIcon type="monitoringApp" />,
renderItem: (props: any) => (
<Link {...props} to={`${baseUrl}/monitoring`} />
),
isSelected: monitoringSelected,
},
]
: []),
{
name: "Permissions",
id: htmlIdGenerator("permissions")(),
Expand Down
21 changes: 6 additions & 15 deletions ui/src/pages/features/FeatureMonitoringTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,11 @@ const FeatureMonitoringTab = () => {
if (!metrics || metrics.length === 0) return null;
const withData = metrics.filter((m) => m.row_count > 0);
const candidates = withData.length > 0 ? withData : metrics;
return candidates.reduce((a, b) =>
a.metric_date > b.metric_date ? a : b,
);
return candidates.reduce((a, b) => (a.metric_date > b.metric_date ? a : b));
})();

const baselineMetric =
baselineMetrics && baselineMetrics.length > 0
? baselineMetrics[0]
: null;
baselineMetrics && baselineMetrics.length > 0 ? baselineMetrics[0] : null;

if (isError || !latestMetric) {
return (
Expand All @@ -66,15 +62,12 @@ const FeatureMonitoringTab = () => {
title={<h3>No Monitoring Data</h3>}
body={
<p>
No monitoring metrics available for this feature. Run a
monitoring compute job to generate data quality metrics.
No monitoring metrics available for this feature. Run a monitoring
compute job to generate data quality metrics.
</p>
}
actions={
<EuiButton
size="s"
href={`/p/${projectName}/monitoring`}
>
<EuiButton size="s" href={`/p/${projectName}/monitoring`}>
Go to Monitoring
</EuiButton>
}
Expand All @@ -91,9 +84,7 @@ const FeatureMonitoringTab = () => {
{isNumeric && latestMetric.histogram && (
<NumericHistogramChart
histogram={latestMetric.histogram as NumericHistogram}
baseline={
baselineMetric?.histogram as NumericHistogram | null
}
baseline={baselineMetric?.histogram as NumericHistogram | null}
title="Distribution"
/>
)}
Expand Down
23 changes: 8 additions & 15 deletions ui/src/pages/monitoring/FeatureMetricsDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,7 @@ const FeatureMetricsDetail = () => {
const navigate = useNavigate();
const [selectedGranularity, setSelectedGranularity] = useState("");

useDocumentTitle(
`${featureName} Monitoring | ${featureViewName} | Feast`,
);
useDocumentTitle(`${featureName} Monitoring | ${featureViewName} | Feast`);

const {
data: metrics,
Expand All @@ -67,9 +65,7 @@ const FeatureMetricsDetail = () => {
);

const baselineMetric =
baselineMetrics && baselineMetrics.length > 0
? baselineMetrics[0]
: null;
baselineMetrics && baselineMetrics.length > 0 ? baselineMetrics[0] : null;

const availableGranularities = useMemo(() => {
const granularities = new Set<string>();
Expand Down Expand Up @@ -97,7 +93,8 @@ const FeatureMetricsDetail = () => {
return options;
}, [availableGranularities, baselineMetric]);

const effectiveGranularity = selectedGranularity || availableGranularities[0] || "";
const effectiveGranularity =
selectedGranularity || availableGranularities[0] || "";

const activeMetric = useMemo(() => {
if (effectiveGranularity === BASELINE_KEY && baselineMetric) {
Expand All @@ -114,9 +111,7 @@ const FeatureMetricsDetail = () => {
a.metric_date > b.metric_date ? a : b,
);
}
return matching.reduce((a, b) =>
a.metric_date > b.metric_date ? a : b,
);
return matching.reduce((a, b) => (a.metric_date > b.metric_date ? a : b));
}, [metrics, effectiveGranularity, baselineMetric]);

const breadcrumbs = [
Expand Down Expand Up @@ -155,8 +150,8 @@ const FeatureMetricsDetail = () => {
<p>
No monitoring metrics found for feature{" "}
<strong>{featureName}</strong> in feature view{" "}
<strong>{featureViewName}</strong>. Run a monitoring
compute job first.
<strong>{featureViewName}</strong>. Run a monitoring compute job
first.
</p>
}
actions={
Expand Down Expand Up @@ -244,9 +239,7 @@ const FeatureMetricsDetail = () => {
</EuiFlexItem>

<EuiFlexItem grow={1}>
<StatsPanel
metric={activeMetric}
/>
<StatsPanel metric={activeMetric} />
</EuiFlexItem>
</EuiFlexGroup>

Expand Down
Loading