Skip to content

Commit 9fe258e

Browse files
author
Aditya Patil
committed
fix: Move UI registry refresh button to layout header
Signed-off-by: Aditya Patil <adpatil@redhat.com>
1 parent eb3cef9 commit 9fe258e

6 files changed

Lines changed: 62 additions & 64 deletions

File tree

docs/how-to-guides/online-server-performance-tuning.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -266,15 +266,6 @@ The `registryTTLSeconds` field on the Operator CR (or `--registry_ttl_sec` CLI f
266266
| Production (low-latency) | `thread` | 300 |
267267
| Production (frequent schema changes) | `thread` | 60 |
268268

269-
### UI refresh
270-
271-
The Feast UI caches the project list using the same registry cache. After running `feast apply` to add a new project, it may take up to `cache_ttl_seconds` before the project appears in the UI.
272-
273-
To see new projects faster:
274-
275-
- **Lower the TTL**: Set `cache_ttl_seconds: 10` (or similar) in your `feature_store.yaml` registry config. This makes all registry consumers — including the UI — pick up changes within 10 seconds.
276-
- **Refresh on demand**: The project selection page has a **Refresh** button that explicitly invalidates the server-side registry cache (`POST /api/registry/refresh`) and reloads the project list without a full page refresh.
277-
278269
---
279270

280271
## Online store selection

docs/reference/alpha-web-ui.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,12 @@ const tabsRegistry = {
153153
```
154154

155155
Examples of custom tabs can be found in the `ui/custom-tabs` folder.
156+
157+
## Refreshing the project list
158+
159+
The Feast UI caches the project list using the same registry cache. After running `feast apply` to add a new project, it may take up to `cache_ttl_seconds` before the project appears in the UI.
160+
161+
To see new projects faster:
162+
163+
- **Lower the TTL**: Set `cache_ttl_seconds: 10` (or similar) in your `feature_store.yaml` registry config. This makes all registry consumers — including the UI — pick up changes within 10 seconds.
164+
- **Refresh on demand**: The project selection page has a **Refresh** button that explicitly invalidates the server-side registry cache (`POST /api/v1/registry/refresh`) and reloads the project list without a full page refresh.

sdk/python/feast/ui_server.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ async def feast_object_not_found_handler(
158158

159159
register_all_routes(rest_app, grpc_handler, store=store)
160160

161+
@rest_app.post("/registry/refresh")
162+
def refresh_registry():
163+
store.refresh_registry()
164+
return Response(status_code=status.HTTP_200_OK)
165+
161166
class PushRequest(BaseModel):
162167
push_source_name: str
163168
df: Dict[str, List]
@@ -889,17 +894,14 @@ def get_app(
889894

890895
ui_dir_ref = importlib_resources.files(__spec__.parent) / "ui/build/" # type: ignore[name-defined, arg-type]
891896
with importlib_resources.as_file(ui_dir_ref) as ui_dir:
892-
pass
897+
projects_dict = _build_projects_list(store, project_id, root_path)
898+
with ui_dir.joinpath("projects-list.json").open(mode="w") as f:
899+
f.write(json.dumps(projects_dict))
893900

894901
@app.get("/projects-list.json")
895902
def get_projects_list():
896903
return _build_projects_list(store, project_id, root_path)
897904

898-
@app.post("/api/registry/refresh")
899-
def refresh_registry():
900-
store.refresh_registry()
901-
return Response(status_code=status.HTTP_200_OK)
902-
903905
@app.get("/api/mlflow-runs")
904906
def get_mlflow_runs(max_results: int = 50):
905907
"""Return MLflow runs linked to this Feast project via auto-logging."""

sdk/python/tests/unit/test_ui_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def test_projects_list_dynamic_refresh(mock_feature_store):
283283

284284

285285
def test_registry_refresh_endpoint(mock_feature_store):
286-
"""POST /api/registry/refresh calls store.refresh_registry()."""
286+
"""POST /api/v1/registry/refresh calls store.refresh_registry()."""
287287
mock_feature_store.refresh_registry = MagicMock()
288288

289289
with tempfile.TemporaryDirectory() as temp_dir:
@@ -293,6 +293,6 @@ def test_registry_refresh_endpoint(mock_feature_store):
293293
app = get_app(mock_feature_store, TEST_PROJECT_NAME)
294294

295295
client = TestClient(app)
296-
resp = client.post("/api/registry/refresh")
296+
resp = client.post("/api/v1/registry/refresh")
297297
assertpy.assert_that(resp.status_code).is_equal_to(EXPECTED_SUCCESS_STATUS)
298298
mock_feature_store.refresh_registry.assert_called_once()

ui/src/pages/Layout.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import React, { useState, useRef, useEffect } from "react";
1+
import React, { useState, useRef, useEffect, useContext } from "react";
22

33
import {
4+
EuiButton,
45
EuiPage,
56
EuiPageSidebar,
67
EuiPageBody,
@@ -18,10 +19,15 @@ import {
1819
EuiIcon,
1920
} from "@elastic/eui";
2021
import { Outlet } from "react-router-dom";
22+
import { useQueryClient } from "react-query";
2123

2224
import RegistryPathContext from "../contexts/RegistryPathContext";
2325
import { useParams } from "react-router-dom";
24-
import { useLoadProjectsList } from "../contexts/ProjectListContext";
26+
import {
27+
useLoadProjectsList,
28+
ProjectListContext,
29+
ProjectsListSchema,
30+
} from "../contexts/ProjectListContext";
2531
import useLoadRegistry from "../queries/useLoadRegistry";
2632

2733
import ProjectSelector from "../components/ProjectSelector";
@@ -39,8 +45,12 @@ const Layout = () => {
3945
let { projectName } = useParams();
4046
const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false);
4147
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
48+
const [refreshing, setRefreshing] = useState(false);
4249
const searchRef = useRef<RegistrySearchRef>(null);
4350
const { user, logout, isAuthEnabled } = useAuth();
51+
const queryClient = useQueryClient();
52+
const projectListCtx = useContext(ProjectListContext);
53+
const basename = projectListCtx?.basename || "";
4454

4555
const { data: projectsData } = useLoadProjectsList();
4656

@@ -146,6 +156,21 @@ const Layout = () => {
146156
]
147157
: [];
148158

159+
const handleRefresh = async () => {
160+
setRefreshing(true);
161+
try {
162+
await fetch(`${basename}/api/v1/registry/refresh`, { method: "POST" });
163+
const res = await fetch(`${basename}/projects-list.json`, {
164+
headers: { "Content-Type": "application/json" },
165+
});
166+
const json = await res.json();
167+
const parsed = ProjectsListSchema.parse(json);
168+
queryClient.setQueryData("feast-projects-list", parsed);
169+
} finally {
170+
setRefreshing(false);
171+
}
172+
};
173+
149174
const handleSearchOpen = () => {
150175
setIsCommandPaletteOpen(true);
151176
};
@@ -242,6 +267,17 @@ const Layout = () => {
242267
)}
243268
{!data && <EuiFlexItem />}
244269

270+
<EuiFlexItem grow={false}>
271+
<EuiButton
272+
iconType="refresh"
273+
onClick={handleRefresh}
274+
isLoading={refreshing}
275+
size="s"
276+
>
277+
Refresh Registry
278+
</EuiButton>
279+
</EuiFlexItem>
280+
245281
{isAuthEnabled && user && (
246282
<EuiFlexItem grow={false}>
247283
<EuiPopover

ui/src/pages/RootProjectSelectionPage.tsx

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import React, { useContext, useEffect, useState } from "react";
1+
import React, { useEffect } from "react";
22
import {
3-
EuiButtonIcon,
43
EuiCard,
54
EuiFlexGrid,
6-
EuiFlexGroup,
75
EuiFlexItem,
86
EuiIcon,
97
EuiSkeletonText,
@@ -12,22 +10,13 @@ import {
1210
EuiTitle,
1311
EuiHorizontalRule,
1412
} from "@elastic/eui";
15-
import {
16-
useLoadProjectsList,
17-
ProjectListContext,
18-
ProjectsListSchema,
19-
} from "../contexts/ProjectListContext";
13+
import { useLoadProjectsList } from "../contexts/ProjectListContext";
2014
import { useNavigate } from "react-router-dom";
21-
import { useQueryClient } from "react-query";
2215
import FeastIconBlue from "../graphics/FeastIconBlue";
2316

2417
const RootProjectSelectionPage = () => {
2518
const { isLoading, isSuccess, data } = useLoadProjectsList();
2619
const navigate = useNavigate();
27-
const queryClient = useQueryClient();
28-
const projectListCtx = useContext(ProjectListContext);
29-
const basename = projectListCtx?.basename || "";
30-
const [refreshing, setRefreshing] = useState(false);
3120

3221
useEffect(() => {
3322
if (data && data.default) {
@@ -41,21 +30,6 @@ const RootProjectSelectionPage = () => {
4130
}
4231
}, [data, navigate]);
4332

44-
const handleRefresh = async () => {
45-
setRefreshing(true);
46-
try {
47-
await fetch(`${basename}/api/registry/refresh`, { method: "POST" });
48-
const res = await fetch(`${basename}/projects-list.json`, {
49-
headers: { "Content-Type": "application/json" },
50-
});
51-
const json = await res.json();
52-
const parsed = ProjectsListSchema.parse(json);
53-
queryClient.setQueryData("feast-projects-list", parsed);
54-
} finally {
55-
setRefreshing(false);
56-
}
57-
};
58-
5933
const projectCards = data?.projects.map((item, index) => {
6034
return (
6135
<EuiFlexItem key={index}>
@@ -74,23 +48,9 @@ const RootProjectSelectionPage = () => {
7448
return (
7549
<EuiPageTemplate panelled>
7650
<EuiPageTemplate.Section>
77-
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
78-
<EuiFlexItem grow={false}>
79-
<EuiTitle size="s">
80-
<h1>Welcome to Feast</h1>
81-
</EuiTitle>
82-
</EuiFlexItem>
83-
<EuiFlexItem grow={false}>
84-
<EuiButtonIcon
85-
iconType="refresh"
86-
aria-label="Refresh projects"
87-
onClick={handleRefresh}
88-
isLoading={refreshing}
89-
display="base"
90-
size="m"
91-
/>
92-
</EuiFlexItem>
93-
</EuiFlexGroup>
51+
<EuiTitle size="s">
52+
<h1>Welcome to Feast</h1>
53+
</EuiTitle>
9454
<EuiText>
9555
<p>Select one of the projects.</p>
9656
</EuiText>

0 commit comments

Comments
 (0)