Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
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
156 changes: 90 additions & 66 deletions ui/src/FeastUISansProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@ import LabelViewInstance from "./pages/label-views/LabelViewInstance";
import PermissionsIndex from "./pages/permissions/Index";
import LineageIndex from "./pages/lineage/Index";
import NoProjectGuard from "./components/NoProjectGuard";
import MonitoringIndex from "./pages/monitoring/Index";
import FeatureMetricsDetail from "./pages/monitoring/FeatureMetricsDetail";

import TabsRegistryContext, {
FeastTabsRegistryInterface,
} from "./custom-tabs/TabsRegistryContext";
import MonitoringContext, {
MonitoringConfig,
} from "./contexts/MonitoringContext";
import CurlGeneratorTab from "./pages/feature-views/CurlGeneratorTab";
import FeatureFlagsContext, {
FeatureFlags,
Expand All @@ -47,6 +52,7 @@ interface FeastUIConfigs {
featureFlags?: FeatureFlags;
projectListPromise?: Promise<any>;
fetchOptions?: FetchOptions;
monitoringConfig?: MonitoringConfig;
}

const defaultProjectListPromise = (basename: string) => {
Expand Down Expand Up @@ -103,6 +109,12 @@ const FeastUISansProvidersInner = ({
fetchOptions: feastUIConfigs?.fetchOptions,
};

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

return (
<EuiProvider colorMode={colorMode}>
<EuiErrorBoundary>
Expand Down Expand Up @@ -138,74 +150,86 @@ const FeastUISansProvidersInner = ({
<FeatureFlagsContext.Provider
value={feastUIConfigs?.featureFlags || {}}
>
<ProjectListContext.Provider value={projectListContext}>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<RootProjectSelectionPage />} />
<Route
path="/p/:projectName/*"
element={<NoProjectGuard />}
>
<Route index element={<ProjectOverviewPage />} />
<Route
path="data-source/"
element={<DatasourceIndex />}
/>
<Route
path="data-source/:dataSourceName/*"
element={<DataSourceInstance />}
/>
<Route path="features/" element={<FeatureListPage />} />
<Route
path="feature-view/"
element={<FeatureViewIndex />}
/>
<Route
path="feature-view/:featureViewName/*"
element={<FeatureViewInstance />}
></Route>
<Route
path="feature-view/:FeatureViewName/feature/:FeatureName/*"
element={<FeatureInstance />}
/>
<Route
path="feature-service/"
element={<FeatureServiceIndex />}
/>
<Route
path="feature-service/:featureServiceName/*"
element={<FeatureServiceInstance />}
/>
<Route path="entity/" element={<EntityIndex />} />
<Route
path="entity/:entityName/*"
element={<EntityInstance />}
/>

<Route path="label-view/" element={<LabelViewIndex />} />
<Route
path="label-view/:labelViewName/*"
element={<LabelViewInstance />}
/>
<Route
path="label-view/:FeatureViewName/label/:FeatureName/*"
element={<FeatureInstance />}
/>
<Route path="data-set/" element={<DatasetIndex />} />
<Route
path="data-set/:datasetName/*"
element={<DatasetInstance />}
/>
<MonitoringContext.Provider value={monitoringConfig}>
<ProjectListContext.Provider value={projectListContext}>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<RootProjectSelectionPage />} />
<Route
path="permissions/"
element={<PermissionsIndex />}
/>
<Route path="lineage/" element={<LineageIndex />} />
path="/p/:projectName/*"
element={<NoProjectGuard />}
>
<Route index element={<ProjectOverviewPage />} />
<Route
path="data-source/"
element={<DatasourceIndex />}
/>
<Route
path="data-source/:dataSourceName/*"
element={<DataSourceInstance />}
/>
<Route path="features/" element={<FeatureListPage />} />
<Route
path="feature-view/"
element={<FeatureViewIndex />}
/>
<Route
path="feature-view/:featureViewName/*"
element={<FeatureViewInstance />}
></Route>
<Route
path="feature-view/:FeatureViewName/feature/:FeatureName/*"
element={<FeatureInstance />}
/>
<Route
path="feature-service/"
element={<FeatureServiceIndex />}
/>
<Route
path="feature-service/:featureServiceName/*"
element={<FeatureServiceInstance />}
/>
<Route path="entity/" element={<EntityIndex />} />
<Route
path="entity/:entityName/*"
element={<EntityInstance />}
/>
<Route
path="label-view/"
element={<LabelViewIndex />}
/>
<Route
path="label-view/:labelViewName/*"
element={<LabelViewInstance />}
/>
<Route
path="label-view/:FeatureViewName/label/:FeatureName/*"
element={<FeatureInstance />}
/>
<Route path="data-set/" element={<DatasetIndex />} />
<Route
path="data-set/:datasetName/*"
element={<DatasetInstance />}
/>
<Route
path="permissions/"
element={<PermissionsIndex />}
/>
<Route path="lineage/" element={<LineageIndex />} />
<Route
path="monitoring/"
element={<MonitoringIndex />}
/>
<Route
path="monitoring/feature/:featureViewName/:featureName"
element={<FeatureMetricsDetail />}
/>
</Route>
</Route>
</Route>
<Route path="*" element={<NoMatch />} />
</Routes>
</ProjectListContext.Provider>
<Route path="*" element={<NoMatch />} />
</Routes>
</ProjectListContext.Provider>
</MonitoringContext.Provider>
</FeatureFlagsContext.Provider>
</TabsRegistryContext.Provider>
</DataModeContext.Provider>
Expand Down
14 changes: 14 additions & 0 deletions ui/src/contexts/MonitoringContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";

interface MonitoringConfig {
apiBaseUrl: string;
enabled: boolean;
}

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: false,
});

export default MonitoringContext;
export type { MonitoringConfig };
10 changes: 10 additions & 0 deletions ui/src/pages/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,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 @@ -185,6 +186,15 @@ const SideNav = () => {
),
isSelected: useMatchSubpath(`${baseUrl}/permissions`),
},
{
name: "Monitoring",
id: htmlIdGenerator("monitoring")(),
icon: <EuiIcon type="monitoringApp" />,
renderItem: (props: any) => (
<Link {...props} to={`${baseUrl}/monitoring`} />
),
isSelected: monitoringSelected,
},
],
},
];
Expand Down
11 changes: 10 additions & 1 deletion ui/src/pages/features/FeatureInstance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { Route, Routes, useNavigate, useParams } from "react-router-dom";
import { EuiPageTemplate } from "@elastic/eui";

import { FeatureIcon } from "../../graphics/FeatureIcon";
import { useMatchExact } from "../../hooks/useMatchSubpath";
import { useMatchExact, useMatchSubpath } from "../../hooks/useMatchSubpath";
import FeatureOverviewTab from "./FeatureOverviewTab";
import FeatureMonitoringTab from "./FeatureMonitoringTab";
import { useDocumentTitle } from "../../hooks/useDocumentTitle";
import {
useFeatureCustomTabs,
Expand Down Expand Up @@ -34,12 +35,20 @@ const FeatureInstance = () => {
navigate("");
},
},
{
label: "Monitoring",
isSelected: useMatchSubpath("monitoring"),
onClick: () => {
navigate("monitoring");
},
},
...customNavigationTabs,
]}
/>
<EuiPageTemplate.Section>
<Routes>
<Route path="/" element={<FeatureOverviewTab />} />
<Route path="/monitoring" element={<FeatureMonitoringTab />} />
{CustomTabRoutes}
</Routes>
</EuiPageTemplate.Section>
Expand Down
Loading
Loading