Skip to content

Commit fa271be

Browse files
authored
feat: Data Quality Monitoring added in feast UI (#6422)
* feat: Feature Store Monitoring UI support is added Signed-off-by: Jitendra Yejare <11752425+jyejare@users.noreply.github.com> * fix: Build, test and review comments Signed-off-by: Jitendra Yejare <11752425+jyejare@users.noreply.github.com> * chore: Tuned UI changes for monitoring on testing Signed-off-by: Jitendra Yejare <11752425+jyejare@users.noreply.github.com> * chore: Almost final distribution/stats changes for UI Signed-off-by: Jitendra Yejare <11752425+jyejare@users.noreply.github.com> * fix: Monitoring backend check and review comments fixed Signed-off-by: Jitendra Yejare <11752425+jyejare@users.noreply.github.com> * chore: Removing unnecessary monitoring config api Signed-off-by: Jitendra Yejare <11752425+jyejare@users.noreply.github.com> --------- Signed-off-by: Jitendra Yejare <11752425+jyejare@users.noreply.github.com>
1 parent 8a0f23c commit fa271be

18 files changed

Lines changed: 3341 additions & 75 deletions

docs/how-to-guides/feature-monitoring.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,16 @@ Done!
4343
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.
4444

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

49-
### Disabling auto-baseline
49+
### Enabling auto-baseline
5050

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

5353
```yaml
54-
DataQualityMonitoring:
55-
auto_baseline: false
54+
data_quality_monitoring:
55+
auto_baseline: true
5656
```
5757
5858
When using the Feast operator, set this in the `FeatureStore` CR:
@@ -63,9 +63,11 @@ kind: FeatureStore
6363
spec:
6464
feastProject: my_project
6565
dataQualityMonitoring:
66-
autoBaseline: false
66+
autoBaseline: true
6767
```
6868

69+
To disable it, set `auto_baseline: false` (or `autoBaseline: false` in the CR).
70+
6971
## 3. Scheduled monitoring with the CLI
7072

7173
### Auto mode (recommended for production)

sdk/python/feast/monitoring/monitoring_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ def compute_baseline(
367367
feature_view=fv,
368368
metrics_list=metrics_list,
369369
metric_date=date.today(),
370-
granularity="daily",
370+
granularity="baseline",
371371
set_baseline=True,
372372
now=now,
373373
)

sdk/python/feast/ui_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def _setup_rest_mode(app: FastAPI, store: "feast.FeatureStore"):
8181
grpc_handler = RegistryServer(store.registry)
8282

8383
rest_app = FastAPI(root_path="/api/v1")
84-
register_all_routes(rest_app, grpc_handler)
84+
register_all_routes(rest_app, grpc_handler, store=store)
8585

8686
class PushRequest(BaseModel):
8787
push_source_name: str

ui/src/FeastUISansProviders.tsx

Lines changed: 90 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@ import LabelViewInstance from "./pages/label-views/LabelViewInstance";
2727
import PermissionsIndex from "./pages/permissions/Index";
2828
import LineageIndex from "./pages/lineage/Index";
2929
import NoProjectGuard from "./components/NoProjectGuard";
30+
import MonitoringIndex from "./pages/monitoring/Index";
31+
import FeatureMetricsDetail from "./pages/monitoring/FeatureMetricsDetail";
3032

3133
import TabsRegistryContext, {
3234
FeastTabsRegistryInterface,
3335
} from "./custom-tabs/TabsRegistryContext";
36+
import MonitoringContext, {
37+
MonitoringConfig,
38+
} from "./contexts/MonitoringContext";
3439
import CurlGeneratorTab from "./pages/feature-views/CurlGeneratorTab";
3540
import FeatureFlagsContext, {
3641
FeatureFlags,
@@ -47,6 +52,7 @@ interface FeastUIConfigs {
4752
featureFlags?: FeatureFlags;
4853
projectListPromise?: Promise<any>;
4954
fetchOptions?: FetchOptions;
55+
monitoringConfig?: MonitoringConfig;
5056
}
5157

5258
const defaultProjectListPromise = (basename: string) => {
@@ -103,6 +109,12 @@ const FeastUISansProvidersInner = ({
103109
fetchOptions: feastUIConfigs?.fetchOptions,
104110
};
105111

112+
const monitoringConfig: MonitoringConfig =
113+
feastUIConfigs?.monitoringConfig || {
114+
apiBaseUrl: "/api/v1",
115+
enabled: true,
116+
};
117+
106118
return (
107119
<EuiProvider colorMode={colorMode}>
108120
<EuiErrorBoundary>
@@ -138,74 +150,86 @@ const FeastUISansProvidersInner = ({
138150
<FeatureFlagsContext.Provider
139151
value={feastUIConfigs?.featureFlags || {}}
140152
>
141-
<ProjectListContext.Provider value={projectListContext}>
142-
<Routes>
143-
<Route path="/" element={<Layout />}>
144-
<Route index element={<RootProjectSelectionPage />} />
145-
<Route
146-
path="/p/:projectName/*"
147-
element={<NoProjectGuard />}
148-
>
149-
<Route index element={<ProjectOverviewPage />} />
150-
<Route
151-
path="data-source/"
152-
element={<DatasourceIndex />}
153-
/>
154-
<Route
155-
path="data-source/:dataSourceName/*"
156-
element={<DataSourceInstance />}
157-
/>
158-
<Route path="features/" element={<FeatureListPage />} />
159-
<Route
160-
path="feature-view/"
161-
element={<FeatureViewIndex />}
162-
/>
163-
<Route
164-
path="feature-view/:featureViewName/*"
165-
element={<FeatureViewInstance />}
166-
></Route>
167-
<Route
168-
path="feature-view/:FeatureViewName/feature/:FeatureName/*"
169-
element={<FeatureInstance />}
170-
/>
171-
<Route
172-
path="feature-service/"
173-
element={<FeatureServiceIndex />}
174-
/>
175-
<Route
176-
path="feature-service/:featureServiceName/*"
177-
element={<FeatureServiceInstance />}
178-
/>
179-
<Route path="entity/" element={<EntityIndex />} />
180-
<Route
181-
path="entity/:entityName/*"
182-
element={<EntityInstance />}
183-
/>
184-
185-
<Route path="label-view/" element={<LabelViewIndex />} />
186-
<Route
187-
path="label-view/:labelViewName/*"
188-
element={<LabelViewInstance />}
189-
/>
190-
<Route
191-
path="label-view/:FeatureViewName/label/:FeatureName/*"
192-
element={<FeatureInstance />}
193-
/>
194-
<Route path="data-set/" element={<DatasetIndex />} />
195-
<Route
196-
path="data-set/:datasetName/*"
197-
element={<DatasetInstance />}
198-
/>
153+
<MonitoringContext.Provider value={monitoringConfig}>
154+
<ProjectListContext.Provider value={projectListContext}>
155+
<Routes>
156+
<Route path="/" element={<Layout />}>
157+
<Route index element={<RootProjectSelectionPage />} />
199158
<Route
200-
path="permissions/"
201-
element={<PermissionsIndex />}
202-
/>
203-
<Route path="lineage/" element={<LineageIndex />} />
159+
path="/p/:projectName/*"
160+
element={<NoProjectGuard />}
161+
>
162+
<Route index element={<ProjectOverviewPage />} />
163+
<Route
164+
path="data-source/"
165+
element={<DatasourceIndex />}
166+
/>
167+
<Route
168+
path="data-source/:dataSourceName/*"
169+
element={<DataSourceInstance />}
170+
/>
171+
<Route path="features/" element={<FeatureListPage />} />
172+
<Route
173+
path="feature-view/"
174+
element={<FeatureViewIndex />}
175+
/>
176+
<Route
177+
path="feature-view/:featureViewName/*"
178+
element={<FeatureViewInstance />}
179+
></Route>
180+
<Route
181+
path="feature-view/:FeatureViewName/feature/:FeatureName/*"
182+
element={<FeatureInstance />}
183+
/>
184+
<Route
185+
path="feature-service/"
186+
element={<FeatureServiceIndex />}
187+
/>
188+
<Route
189+
path="feature-service/:featureServiceName/*"
190+
element={<FeatureServiceInstance />}
191+
/>
192+
<Route path="entity/" element={<EntityIndex />} />
193+
<Route
194+
path="entity/:entityName/*"
195+
element={<EntityInstance />}
196+
/>
197+
<Route
198+
path="label-view/"
199+
element={<LabelViewIndex />}
200+
/>
201+
<Route
202+
path="label-view/:labelViewName/*"
203+
element={<LabelViewInstance />}
204+
/>
205+
<Route
206+
path="label-view/:FeatureViewName/label/:FeatureName/*"
207+
element={<FeatureInstance />}
208+
/>
209+
<Route path="data-set/" element={<DatasetIndex />} />
210+
<Route
211+
path="data-set/:datasetName/*"
212+
element={<DatasetInstance />}
213+
/>
214+
<Route
215+
path="permissions/"
216+
element={<PermissionsIndex />}
217+
/>
218+
<Route path="lineage/" element={<LineageIndex />} />
219+
<Route
220+
path="monitoring/"
221+
element={<MonitoringIndex />}
222+
/>
223+
<Route
224+
path="monitoring/feature/:featureViewName/:featureName"
225+
element={<FeatureMetricsDetail />}
226+
/>
227+
</Route>
204228
</Route>
205-
</Route>
206-
<Route path="*" element={<NoMatch />} />
207-
</Routes>
208-
</ProjectListContext.Provider>
229+
<Route path="*" element={<NoMatch />} />
230+
</Routes>
231+
</ProjectListContext.Provider>
232+
</MonitoringContext.Provider>
209233
</FeatureFlagsContext.Provider>
210234
</TabsRegistryContext.Provider>
211235
</DataModeContext.Provider>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from "react";
2+
3+
interface MonitoringConfig {
4+
apiBaseUrl: string;
5+
enabled: boolean;
6+
}
7+
8+
const MonitoringContext = React.createContext<MonitoringConfig>({
9+
apiBaseUrl: "/api/v1",
10+
enabled: false,
11+
});
12+
13+
export default MonitoringContext;
14+
export type { MonitoringConfig };

ui/src/pages/Sidebar.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const SideNav = () => {
9999
const labelViewsLabel = `Label Views ${lvSuccess && labelViews && labelViews.length > 0 ? `(${labelViews.length})` : ""}`;
100100

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

103104
const sideNav: React.ComponentProps<typeof EuiSideNav>["items"] = [
104105
{
@@ -185,6 +186,15 @@ const SideNav = () => {
185186
),
186187
isSelected: useMatchSubpath(`${baseUrl}/permissions`),
187188
},
189+
{
190+
name: "Monitoring",
191+
id: htmlIdGenerator("monitoring")(),
192+
icon: <EuiIcon type="monitoringApp" />,
193+
renderItem: (props: any) => (
194+
<Link {...props} to={`${baseUrl}/monitoring`} />
195+
),
196+
isSelected: monitoringSelected,
197+
},
188198
],
189199
},
190200
];

ui/src/pages/features/FeatureInstance.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { Route, Routes, useNavigate, useParams } from "react-router-dom";
33
import { EuiPageTemplate } from "@elastic/eui";
44

55
import { FeatureIcon } from "../../graphics/FeatureIcon";
6-
import { useMatchExact } from "../../hooks/useMatchSubpath";
6+
import { useMatchExact, useMatchSubpath } from "../../hooks/useMatchSubpath";
77
import FeatureOverviewTab from "./FeatureOverviewTab";
8+
import FeatureMonitoringTab from "./FeatureMonitoringTab";
89
import { useDocumentTitle } from "../../hooks/useDocumentTitle";
910
import {
1011
useFeatureCustomTabs,
@@ -34,12 +35,20 @@ const FeatureInstance = () => {
3435
navigate("");
3536
},
3637
},
38+
{
39+
label: "Monitoring",
40+
isSelected: useMatchSubpath("monitoring"),
41+
onClick: () => {
42+
navigate("monitoring");
43+
},
44+
},
3745
...customNavigationTabs,
3846
]}
3947
/>
4048
<EuiPageTemplate.Section>
4149
<Routes>
4250
<Route path="/" element={<FeatureOverviewTab />} />
51+
<Route path="/monitoring" element={<FeatureMonitoringTab />} />
4352
{CustomTabRoutes}
4453
</Routes>
4554
</EuiPageTemplate.Section>

0 commit comments

Comments
 (0)