@@ -15,56 +15,115 @@ import {
1515 EuiDescriptionListDescription ,
1616 EuiSpacer ,
1717} from "@elastic/eui" ;
18- import React , { useContext , useState } from "react" ;
18+ import React , { useState } from "react" ;
1919import { useParams } from "react-router-dom" ;
20- import PermissionsDisplay from "../../components/PermissionsDisplay" ;
2120import DataSourceFormModal , {
2221 DataSourceFormData ,
2322} from "../../components/DataSourceFormModal" ;
24- import RegistryPathContext from "../../contexts/RegistryPathContext" ;
25- import { FEAST_FCO_TYPES } from "../../parsers/types" ;
2623import { feast } from "../../protos" ;
27- import useLoadRegistry from "../../queries/useLoadRegistry" ;
28- import { getEntityPermissions } from "../../utils/permissionUtils" ;
24+ import { useApplyDataSource } from "../../queries/mutations/useDataSourceMutations" ;
2925import BatchSourcePropertiesView from "./BatchSourcePropertiesView" ;
3026import FeatureViewEdgesList from "../entities/FeatureViewEdgesList" ;
3127import RequestDataSourceSchemaTable from "./RequestDataSourceSchemaTable" ;
3228import useLoadDataSource from "./useLoadDataSource" ;
3329import { useUIVersion } from "../../contexts/UIVersionContext" ;
3430
35- const buildEditFormData = ( ds : feast . core . IDataSource ) : DataSourceFormData => {
36- const tags = ds . tags
37- ? Object . entries ( ds . tags ) . map ( ( [ key , value ] ) => ( { key, value } ) )
31+ const buildEditFormData = ( ds : any ) : DataSourceFormData => {
32+ const spec = ds . spec || ds ;
33+ const tags = spec . tags
34+ ? Object . entries ( spec . tags ) . map ( ( [ key , value ] ) => ( {
35+ key,
36+ value : value as string ,
37+ } ) )
3838 : [ ] ;
3939
4040 return {
41- name : ds . name || "" ,
42- description : ds . description || "" ,
43- owner : ds . owner || "" ,
44- sourceType : String ( ds . type ?? 0 ) ,
45- timestampField : ds . timestampField || "" ,
46- createdTimestampColumn : ds . createdTimestampColumn || "" ,
41+ name : spec . name || ds . name || "" ,
42+ description : spec . description || ds . description || "" ,
43+ owner : spec . owner || ds . owner || "" ,
44+ sourceType : String ( spec . type ?? ds . type ?? 0 ) ,
45+ timestampField : spec . timestampField || ds . timestampField || "" ,
46+ createdTimestampColumn :
47+ spec . createdTimestampColumn || ds . createdTimestampColumn || "" ,
4748 tags,
48- fileUri : ds . fileOptions ?. uri || "" ,
49- bigqueryTable : ds . bigqueryOptions ?. table || "" ,
50- bigqueryQuery : ds . bigqueryOptions ?. query || "" ,
51- snowflakeTable : ds . snowflakeOptions ?. table || "" ,
52- snowflakeDatabase : ds . snowflakeOptions ?. database || "" ,
53- snowflakeSchema : ds . snowflakeOptions ?. schema || "" ,
54- redshiftTable : ds . redshiftOptions ?. table || "" ,
55- redshiftDatabase : ds . redshiftOptions ?. database || "" ,
56- redshiftSchema : ds . redshiftOptions ?. schema || "" ,
57- kafkaBootstrapServers : ds . kafkaOptions ?. kafkaBootstrapServers || "" ,
58- kafkaTopic : ds . kafkaOptions ?. topic || "" ,
59- sparkTable : ds . sparkOptions ?. table || "" ,
60- sparkPath : ds . sparkOptions ?. path || "" ,
49+ fileUri : spec . fileOptions ?. uri || ds . fileOptions ?. uri || "" ,
50+ bigqueryTable :
51+ spec . bigqueryOptions ?. table || ds . bigqueryOptions ?. table || "" ,
52+ bigqueryQuery :
53+ spec . bigqueryOptions ?. query || ds . bigqueryOptions ?. query || "" ,
54+ snowflakeTable :
55+ spec . snowflakeOptions ?. table || ds . snowflakeOptions ?. table || "" ,
56+ snowflakeDatabase :
57+ spec . snowflakeOptions ?. database || ds . snowflakeOptions ?. database || "" ,
58+ snowflakeSchema :
59+ spec . snowflakeOptions ?. schema || ds . snowflakeOptions ?. schema || "" ,
60+ redshiftTable :
61+ spec . redshiftOptions ?. table || ds . redshiftOptions ?. table || "" ,
62+ redshiftDatabase :
63+ spec . redshiftOptions ?. database || ds . redshiftOptions ?. database || "" ,
64+ redshiftSchema :
65+ spec . redshiftOptions ?. schema || ds . redshiftOptions ?. schema || "" ,
66+ kafkaBootstrapServers :
67+ spec . kafkaOptions ?. kafkaBootstrapServers ||
68+ ds . kafkaOptions ?. kafkaBootstrapServers ||
69+ "" ,
70+ kafkaTopic : spec . kafkaOptions ?. topic || ds . kafkaOptions ?. topic || "" ,
71+ sparkTable : spec . sparkOptions ?. table || ds . sparkOptions ?. table || "" ,
72+ sparkPath : spec . sparkOptions ?. path || ds . sparkOptions ?. path || "" ,
6173 } ;
6274} ;
6375
76+ const formDataToPayload = ( formData : DataSourceFormData , project : string ) => {
77+ const payload : Record < string , any > = {
78+ name : formData . name ,
79+ project,
80+ type : parseInt ( formData . sourceType , 10 ) ,
81+ timestamp_field : formData . timestampField ,
82+ created_timestamp_column : formData . createdTimestampColumn ,
83+ description : formData . description ,
84+ owner : formData . owner ,
85+ tags : Object . fromEntries (
86+ formData . tags . filter ( ( t ) => t . key . trim ( ) ) . map ( ( t ) => [ t . key , t . value ] ) ,
87+ ) ,
88+ } ;
89+
90+ const st = formData . sourceType ;
91+ if ( st === String ( feast . core . DataSource . SourceType . BATCH_FILE ) ) {
92+ payload . file_options = { uri : formData . fileUri } ;
93+ } else if ( st === String ( feast . core . DataSource . SourceType . BATCH_BIGQUERY ) ) {
94+ payload . bigquery_options = {
95+ table : formData . bigqueryTable ,
96+ query : formData . bigqueryQuery ,
97+ } ;
98+ } else if ( st === String ( feast . core . DataSource . SourceType . BATCH_SNOWFLAKE ) ) {
99+ payload . snowflake_options = {
100+ table : formData . snowflakeTable ,
101+ database : formData . snowflakeDatabase ,
102+ schema_ : formData . snowflakeSchema ,
103+ } ;
104+ } else if ( st === String ( feast . core . DataSource . SourceType . BATCH_REDSHIFT ) ) {
105+ payload . redshift_options = {
106+ table : formData . redshiftTable ,
107+ database : formData . redshiftDatabase ,
108+ schema_ : formData . redshiftSchema ,
109+ } ;
110+ } else if ( st === String ( feast . core . DataSource . SourceType . STREAM_KAFKA ) ) {
111+ payload . kafka_options = {
112+ kafka_bootstrap_servers : formData . kafkaBootstrapServers ,
113+ topic : formData . kafkaTopic ,
114+ } ;
115+ } else if ( st === String ( feast . core . DataSource . SourceType . BATCH_SPARK ) ) {
116+ payload . spark_options = {
117+ table : formData . sparkTable ,
118+ path : formData . sparkPath ,
119+ } ;
120+ }
121+
122+ return payload ;
123+ } ;
124+
64125const DataSourceOverviewTab = ( ) => {
65- let { dataSourceName, projectName } = useParams ( ) ;
66- const registryUrl = useContext ( RegistryPathContext ) ;
67- const registryQuery = useLoadRegistry ( registryUrl , projectName ) ;
126+ const { dataSourceName, projectName } = useParams ( ) ;
68127
69128 const dsName = dataSourceName === undefined ? "" : dataSourceName ;
70129 const { isLoading, isSuccess, isError, data, consumingFeatureViews } =
@@ -74,16 +133,32 @@ const DataSourceOverviewTab = () => {
74133 const { isV2 } = useUIVersion ( ) ;
75134 const [ isEditModalOpen , setIsEditModalOpen ] = useState ( false ) ;
76135 const [ successMessage , setSuccessMessage ] = useState < string | null > ( null ) ;
136+ const [ errorMessage , setErrorMessage ] = useState < string | null > ( null ) ;
137+ const applyDataSource = useApplyDataSource ( ) ;
77138
78139 const handleEditSubmit = ( formData : DataSourceFormData ) => {
79- console . log ( "Data source edit payload:" , formData ) ;
80- setIsEditModalOpen ( false ) ;
81- setSuccessMessage (
82- `Changes to "${ formData . name } " are ready to apply. Backend integration coming soon.` ,
83- ) ;
84- setTimeout ( ( ) => setSuccessMessage ( null ) , 5000 ) ;
140+ const payload = formDataToPayload ( formData , projectName || "" ) ;
141+ applyDataSource . mutate ( payload as any , {
142+ onSuccess : ( ) => {
143+ setIsEditModalOpen ( false ) ;
144+ setErrorMessage ( null ) ;
145+ setSuccessMessage (
146+ `Data source "${ formData . name } " updated successfully.` ,
147+ ) ;
148+ setTimeout ( ( ) => setSuccessMessage ( null ) , 5000 ) ;
149+ } ,
150+ onError : ( err : unknown ) => {
151+ const message =
152+ err instanceof Error ? err . message : "An unexpected error occurred." ;
153+ setErrorMessage ( message ) ;
154+ setTimeout ( ( ) => setErrorMessage ( null ) , 8000 ) ;
155+ } ,
156+ } ) ;
85157 } ;
86158
159+ const spec = data ?. spec || data ;
160+ const sourceType = spec ?. type ;
161+
87162 return (
88163 < React . Fragment >
89164 { isLoading && (
@@ -106,6 +181,17 @@ const DataSourceOverviewTab = () => {
106181 < EuiSpacer size = "m" />
107182 </ >
108183 ) }
184+ { errorMessage && (
185+ < >
186+ < EuiCallOut
187+ title = { errorMessage }
188+ color = "danger"
189+ iconType = "alert"
190+ size = "s"
191+ />
192+ < EuiSpacer size = "m" />
193+ </ >
194+ ) }
109195 { isV2 && (
110196 < >
111197 < EuiFlexGroup justifyContent = "flexEnd" >
@@ -130,16 +216,16 @@ const DataSourceOverviewTab = () => {
130216 < h2 > Properties</ h2 >
131217 </ EuiTitle >
132218 < EuiHorizontalRule margin = "xs" />
133- { data . fileOptions || data . bigqueryOptions ? (
134- < BatchSourcePropertiesView batchSource = { data } />
135- ) : data . type ? (
219+ { spec ? .fileOptions || spec ? .bigqueryOptions ? (
220+ < BatchSourcePropertiesView batchSource = { spec } />
221+ ) : sourceType ? (
136222 < React . Fragment >
137223 < EuiDescriptionList >
138224 < EuiDescriptionListTitle >
139225 Source Type
140226 </ EuiDescriptionListTitle >
141227 < EuiDescriptionListDescription >
142- { feast . core . DataSource . SourceType [ data . type ] }
228+ { sourceType }
143229 </ EuiDescriptionListDescription >
144230 </ EuiDescriptionList >
145231 </ React . Fragment >
@@ -152,20 +238,18 @@ const DataSourceOverviewTab = () => {
152238 < EuiSpacer size = "m" />
153239 < EuiFlexGroup >
154240 < EuiFlexItem >
155- { data . requestDataOptions ? (
241+ { spec ? .requestDataOptions ? (
156242 < EuiPanel hasBorder = { true } >
157243 < EuiTitle size = "xs" >
158244 < h2 > Request Source Schema</ h2 >
159245 </ EuiTitle >
160246 < EuiHorizontalRule margin = "xs" > </ EuiHorizontalRule >
161247 < RequestDataSourceSchemaTable
162248 fields = {
163- data ?. requestDataOptions ?. schema ! . map ( ( obj ) => {
164- return {
165- fieldName : obj . name ! ,
166- valueType : obj . valueType ! ,
167- } ;
168- } ) !
249+ spec . requestDataOptions . schema ?. map ( ( obj : any ) => ( {
250+ fieldName : obj . name ,
251+ valueType : obj . valueType ,
252+ } ) ) || [ ]
169253 }
170254 />
171255 </ EuiPanel >
@@ -183,32 +267,12 @@ const DataSourceOverviewTab = () => {
183267 < EuiHorizontalRule margin = "xs" > </ EuiHorizontalRule >
184268 { consumingFeatureViews && consumingFeatureViews . length > 0 ? (
185269 < FeatureViewEdgesList
186- fvNames = { consumingFeatureViews . map ( ( f ) => {
187- return f . target . name ;
188- } ) }
189- />
190- ) : (
191- < EuiText > No consuming feature views</ EuiText >
192- ) }
193- </ EuiPanel >
194- < EuiSpacer size = "m" />
195- < EuiPanel hasBorder = { true } >
196- < EuiTitle size = "xs" >
197- < h2 > Permissions</ h2 >
198- </ EuiTitle >
199- < EuiHorizontalRule margin = "xs" > </ EuiHorizontalRule >
200- { registryQuery . data ?. permissions ? (
201- < PermissionsDisplay
202- permissions = { getEntityPermissions (
203- registryQuery . data . permissions ,
204- FEAST_FCO_TYPES . dataSource ,
205- dsName ,
270+ fvNames = { consumingFeatureViews . map (
271+ ( f : any ) => f . target . name ,
206272 ) }
207273 />
208274 ) : (
209- < EuiText >
210- No permissions defined for this data source.
211- </ EuiText >
275+ < EuiText > No consuming feature views</ EuiText >
212276 ) }
213277 </ EuiPanel >
214278 </ EuiFlexItem >
0 commit comments