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 REST UI mutation consistency
  • Loading branch information
franciscojavierarceo committed Jun 8, 2026
commit 2a2e826330c8a0789fe7ea2b87b10ff7ab13cf65
9 changes: 8 additions & 1 deletion sdk/python/feast/api/registry/rest/feature_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
class FeatureModel(BaseModel):
name: str
value_type: int = 2
description: Optional[str] = ""


class ApplyFeatureViewRequestBody(BaseModel):
Expand Down Expand Up @@ -307,7 +308,13 @@ def list_all_feature_views(
def apply_feature_view(body: ApplyFeatureViewRequestBody):
feature_specs = []
for f in body.features or []:
feature_specs.append(FeatureSpecV2(name=f.name, value_type=f.value_type))
feature_specs.append(
FeatureSpecV2(
name=f.name,
value_type=f.value_type,
description=f.description or "",
)
)

batch_source_proto = (
DataSourceProto(name=body.batch_source) if body.batch_source else None
Expand Down
17 changes: 14 additions & 3 deletions sdk/python/tests/unit/api/test_api_rest_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2091,8 +2091,16 @@ def test_apply_and_delete_feature_view_via_rest(fastapi_test_app):
"project": "demo_project",
"entities": ["user_id"],
"features": [
{"name": "trip_count", "value_type": 2},
{"name": "avg_rating", "value_type": 4},
{
"name": "trip_count",
"value_type": 2,
"description": "Number of completed trips",
},
{
"name": "avg_rating",
"value_type": 4,
"description": "Average driver rating",
},
],
"ttl_seconds": 86400,
"online": True,
Expand All @@ -2107,7 +2115,10 @@ def test_apply_and_delete_feature_view_via_rest(fastapi_test_app):
# Verify it exists
response = fastapi_test_app.get("/feature_views/driver_stats?project=demo_project")
assert response.status_code == 200
assert response.json()["spec"]["name"] == "driver_stats"
spec = response.json()["spec"]
assert spec["name"] == "driver_stats"
assert spec["features"][0]["description"] == "Number of completed trips"
assert spec["features"][1]["description"] == "Average driver rating"

# Delete it
response = fastapi_test_app.delete(
Expand Down
11 changes: 11 additions & 0 deletions ui/src/components/FeatureViewFormModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,22 @@ const FeatureViewFormModal: React.FC<FeatureViewFormModalProps> = ({
database: dsData.snowflakeDatabase,
schema_: dsData.snowflakeSchema,
};
} else if (st === String(feast.core.DataSource.SourceType.BATCH_REDSHIFT)) {
payload.redshift_options = {
table: dsData.redshiftTable,
database: dsData.redshiftDatabase,
schema_: dsData.redshiftSchema,
};
} else if (st === String(feast.core.DataSource.SourceType.STREAM_KAFKA)) {
payload.kafka_options = {
kafka_bootstrap_servers: dsData.kafkaBootstrapServers,
topic: dsData.kafkaTopic,
};
} else if (st === String(feast.core.DataSource.SourceType.BATCH_SPARK)) {
payload.spark_options = {
table: dsData.sparkTable,
path: dsData.sparkPath,
};
}

applyDataSource.mutate(payload as any, {
Expand Down
23 changes: 13 additions & 10 deletions ui/src/pages/data-sources/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ const useLoadDatasources = () => {
};

const filterFn = (data: any[], searchTokens: string[]) => {
let filteredByTags = data;

if (searchTokens.length) {
return data.filter((entry) => {
const name = entry.name || entry.spec?.name || "";
Expand Down Expand Up @@ -105,6 +103,7 @@ const formDataToPayload = (formData: DataSourceFormData, project: string) => {
const Index = () => {
const { projectName } = useParams();
const { isLoading, isSuccess, isError, data } = useLoadDatasources();
const isAllProjects = projectName === "all";

const [isModalOpen, setIsModalOpen] = useState(false);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
Expand Down Expand Up @@ -144,14 +143,18 @@ const Index = () => {
iconType={DataSourceIcon}
pageTitle="Data Sources"
rightSideItems={[
<EuiButton
fill
iconType="plus"
onClick={() => setIsModalOpen(true)}
key="create"
>
Create Data Source
</EuiButton>,
...(isAllProjects
? []
: [
<EuiButton
fill
iconType="plus"
onClick={() => setIsModalOpen(true)}
key="create"
>
Create Data Source
</EuiButton>,
]),
<ExportButton
data={filterResult ?? []}
fileName="data_sources"
Expand Down
21 changes: 13 additions & 8 deletions ui/src/pages/entities/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const formDataToPayload = (formData: EntityFormData, project: string) => ({
const Index = () => {
const { projectName } = useParams();
const { isLoading, isSuccess, isError, data } = useLoadEntities();
const isAllProjects = projectName === "all";

const [isModalOpen, setIsModalOpen] = useState(false);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
Expand Down Expand Up @@ -81,14 +82,18 @@ const Index = () => {
iconType={EntityIcon}
pageTitle="Entities"
rightSideItems={[
<EuiButton
fill
iconType="plus"
onClick={() => setIsModalOpen(true)}
key="create"
>
Create Entity
</EuiButton>,
...(isAllProjects
? []
: [
<EuiButton
fill
iconType="plus"
onClick={() => setIsModalOpen(true)}
key="create"
>
Create Entity
</EuiButton>,
]),
<ExportButton
data={data ?? []}
fileName="entities"
Expand Down
28 changes: 15 additions & 13 deletions ui/src/pages/feature-views/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ import {
useSearchQuery,
useTagsWithSuggestions,
} from "../../hooks/useSearchInputWithTags";
import {
FEAST_FV_TYPES,
genericFVType,
regularFVInterface,
} from "../../parsers/mergedFVTypes";
import { genericFVType, regularFVInterface } from "../../parsers/mergedFVTypes";
import { useDocumentTitle } from "../../hooks/useDocumentTitle";
import FeatureViewIndexEmptyState from "./FeatureViewIndexEmptyState";
import { useFeatureViewTagsAggregation } from "../../hooks/useTagsAggregation";
Expand Down Expand Up @@ -111,6 +107,7 @@ const formDataToPayload = (formData: FeatureViewFormData, project: string) => ({
features: formData.features.map((f) => ({
name: f.name,
value_type: parseInt(f.valueType, 10),
description: f.description,
})),
batch_source: formData.batchSource,
ttl_seconds: formData.ttlValue * (TTL_UNITS[formData.ttlUnit] || 1),
Expand All @@ -125,6 +122,7 @@ const formDataToPayload = (formData: FeatureViewFormData, project: string) => ({
const Index = () => {
const { projectName } = useParams();
const { isLoading, isSuccess, isError, data } = useLoadFeatureViews();
const isAllProjects = projectName === "all";

const entitiesQuery = useResourceQuery<any[]>({
resourceType: "entities-list-fv-prereq",
Expand Down Expand Up @@ -210,14 +208,18 @@ const Index = () => {
iconType={FeatureViewIcon}
pageTitle="Feature Views"
rightSideItems={[
<EuiButton
fill
iconType="plus"
onClick={handleCreateClick}
key="create"
>
Create Feature View
</EuiButton>,
...(isAllProjects
? []
: [
<EuiButton
fill
iconType="plus"
onClick={handleCreateClick}
key="create"
>
Create Feature View
</EuiButton>,
]),
<ExportButton
data={filterResult ?? []}
fileName="feature_views"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const RegularFeatureViewOverviewTab = ({
features: formData.features.map((f) => ({
name: f.name,
value_type: parseInt(f.valueType, 10),
description: f.description,
})),
batch_source: formData.batchSource,
ttl_seconds: formData.ttlValue * (TTL_UNITS[formData.ttlUnit] || 1),
Expand Down
2 changes: 2 additions & 0 deletions ui/src/queries/mutations/useDataSourceMutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const useApplyDataSource = () => {

return useMutation(applyDataSource, {
onSuccess: () => {
queryClient.invalidateQueries(["rest"]);
queryClient.invalidateQueries(["data-sources-rest"]);
queryClient.invalidateQueries(["data-source-rest"]);
},
Expand All @@ -87,6 +88,7 @@ const useDeleteDataSource = () => {

return useMutation(deleteDataSource, {
onSuccess: () => {
queryClient.invalidateQueries(["rest"]);
queryClient.invalidateQueries(["data-sources-rest"]);
queryClient.invalidateQueries(["data-source-rest"]);
},
Expand Down
2 changes: 2 additions & 0 deletions ui/src/queries/mutations/useEntityMutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const useApplyEntity = () => {

return useMutation(applyEntity, {
onSuccess: () => {
queryClient.invalidateQueries(["rest"]);
queryClient.invalidateQueries(["entities-rest"]);
queryClient.invalidateQueries(["entity-rest"]);
},
Expand All @@ -80,6 +81,7 @@ const useDeleteEntity = () => {

return useMutation(deleteEntity, {
onSuccess: () => {
queryClient.invalidateQueries(["rest"]);
queryClient.invalidateQueries(["entities-rest"]);
queryClient.invalidateQueries(["entity-rest"]);
},
Expand Down
3 changes: 3 additions & 0 deletions ui/src/queries/mutations/useFeatureViewMutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from "react-query";
interface FeaturePayload {
name: string;
value_type: number;
description?: string;
}

interface ApplyFeatureViewPayload {
Expand Down Expand Up @@ -77,6 +78,7 @@ const useApplyFeatureView = () => {

return useMutation(applyFeatureView, {
onSuccess: () => {
queryClient.invalidateQueries(["rest"]);
queryClient.invalidateQueries(["feature-views-rest"]);
queryClient.invalidateQueries(["feature-view-rest"]);
},
Expand All @@ -88,6 +90,7 @@ const useDeleteFeatureView = () => {

return useMutation(deleteFeatureView, {
onSuccess: () => {
queryClient.invalidateQueries(["rest"]);
queryClient.invalidateQueries(["feature-views-rest"]);
queryClient.invalidateQueries(["feature-view-rest"]);
},
Expand Down
Loading