@@ -23,8 +23,10 @@ import {
2323 IconButton ,
2424 Input ,
2525 RadioGroup ,
26+ Text ,
2627 useToast ,
2728} from '@redpanda-data/ui' ;
29+ import { useQueryClient } from '@tanstack/react-query' ;
2830import { TrashIcon } from 'components/icons' ;
2931import { InfoIcon } from 'lucide-react' ;
3032import { observable } from 'mobx' ;
@@ -150,6 +152,16 @@ export class SchemaAddVersionPage extends PageComponent<{ subjectName: string }>
150152 this . editorState . references = schema . references ;
151153 this . editorState . strategy = 'CUSTOM' ;
152154 this . editorState . userInput = subject . name ;
155+
156+ // Load existing metadata properties for editing
157+ if ( schema . metadata ?. properties ) {
158+ this . editorState . metadataProperties = Object . entries ( schema . metadata . properties ) . map ( ( [ key , value ] ) => ( {
159+ key,
160+ value,
161+ } ) ) ;
162+ // Add an empty row for adding new properties
163+ this . editorState . metadataProperties . push ( { key : '' , value : '' } ) ;
164+ }
153165 }
154166
155167 return (
@@ -176,6 +188,7 @@ const SchemaPageButtons = observer(
176188 editorState : SchemaEditorStateHelper ;
177189 } ) => {
178190 const toast = useToast ( ) ;
191+ const queryClient = useQueryClient ( ) ;
179192 const [ isValidating , setValidating ] = useState ( false ) ;
180193 const [ isCreating , setCreating ] = useState ( false ) ;
181194 const [ persistentValidationError , setPersistentValidationError ] = useState < {
@@ -248,6 +261,7 @@ const SchemaPageButtons = observer(
248261 schemaType : editorState . format as SchemaTypeType ,
249262 schema : editorState . schemaText ,
250263 references : editorState . references . filter ( ( x ) => x . name && x . subject ) ,
264+ metadata : editorState . computedMetadata ,
251265 params : {
252266 normalize : editorState . normalize ,
253267 } ,
@@ -256,15 +270,17 @@ const SchemaPageButtons = observer(
256270
257271 await api . refreshSchemaDetails ( subjectName , true ) ;
258272
259- // success: navigate to details
260- const latestVersion = api . schemaDetails . get ( subjectName ) ?. latestActiveVersion ;
273+ // Invalidate React Query cache so details page shows latest data
274+ await queryClient . invalidateQueries ( {
275+ queryKey : [ 'schemaRegistry' , 'subjects' , subjectName , 'details' ] ,
276+ } ) ;
277+
278+ // success: navigate to details with "latest" so it picks up the new version
261279 // biome-ignore lint/suspicious/noConsole: intentional console usage
262280 console . log ( 'schema created' , { response : r } ) ;
263281 // biome-ignore lint/suspicious/noConsole: intentional console usage
264- console . log ( 'navigating to details' , { subjectName, latestVersion } ) ;
265- appGlobal . historyReplace (
266- `/schema-registry/subjects/${ encodeURIComponent ( subjectName ) } ?version=${ latestVersion } `
267- ) ;
282+ console . log ( 'navigating to details' , { subjectName } ) ;
283+ appGlobal . historyReplace ( `/schema-registry/subjects/${ encodeURIComponent ( subjectName ) } ?version=latest` ) ;
268284 } catch ( err ) {
269285 // error: open modal
270286 // biome-ignore lint/suspicious/noConsole: intentional console usage
@@ -547,6 +563,13 @@ const SchemaEditor = observer((p: { state: SchemaEditorStateHelper; mode: 'CREAT
547563 { /* <Text>This is an example help text about the references list, to be updated later</Text> */ }
548564
549565 < ReferencesEditor state = { state } />
566+
567+ < Heading mt = "8" variant = "lg" >
568+ Schema metadata
569+ </ Heading >
570+ < Text > Optional key-value properties to associate with this schema.</ Text >
571+
572+ < MetadataPropertiesEditor state = { state } />
550573 </ Flex >
551574 </ >
552575 ) ;
@@ -636,6 +659,59 @@ const ReferencesEditor = observer((p: { state: SchemaEditorStateHelper }) => {
636659 ) ;
637660} ) ;
638661
662+ const MetadataPropertiesEditor = observer ( ( p : { state : SchemaEditorStateHelper } ) => {
663+ const { state } = p ;
664+ const props = state . metadataProperties ;
665+
666+ const renderRow = ( prop : { key : string ; value : string } , index : number ) => (
667+ < Flex alignItems = "flex-end" gap = "4" key = { index } >
668+ < FormField label = "Key" >
669+ < Input
670+ data-testid = { `schema-create-metadata-key-input-${ index } ` }
671+ onChange = { ( e ) => {
672+ prop . key = e . target . value ;
673+ } }
674+ placeholder = "e.g. owner"
675+ value = { prop . key }
676+ />
677+ </ FormField >
678+ < FormField label = "Value" >
679+ < Input
680+ data-testid = { `schema-create-metadata-value-input-${ index } ` }
681+ onChange = { ( e ) => {
682+ prop . value = e . target . value ;
683+ } }
684+ placeholder = "e.g. team-platform"
685+ value = { prop . value }
686+ />
687+ </ FormField >
688+ < IconButton
689+ aria-label = "delete"
690+ data-testid = { `schema-create-metadata-delete-btn-${ index } ` }
691+ icon = { < TrashIcon fontSize = "19px" /> }
692+ onClick = { ( ) => props . remove ( prop ) }
693+ variant = "ghost"
694+ />
695+ </ Flex >
696+ ) ;
697+
698+ return (
699+ < Flex direction = "column" gap = "4" >
700+ { props . map ( ( x , index ) => renderRow ( x , index ) ) }
701+
702+ < Button
703+ data-testid = "schema-create-add-metadata-btn"
704+ onClick = { ( ) => props . push ( { key : '' , value : '' } ) }
705+ size = "sm"
706+ variant = "outline"
707+ width = "fit-content"
708+ >
709+ Add property
710+ </ Button >
711+ </ Flex >
712+ ) ;
713+ } ) ;
714+
639715function createSchemaState ( ) {
640716 return observable ( {
641717 strategy : 'TOPIC' as
@@ -654,6 +730,17 @@ function createSchemaState() {
654730 version : number ;
655731 } [ ] ,
656732 normalize : false ,
733+ metadataProperties : [ { key : '' , value : '' } ] as { key : string ; value : string } [ ] ,
734+
735+ get computedMetadata ( ) : { properties : Record < string , string > } | undefined {
736+ const properties : Record < string , string > = { } ;
737+ for ( const prop of this . metadataProperties ) {
738+ if ( prop . key && prop . value ) {
739+ properties [ prop . key ] = prop . value ;
740+ }
741+ }
742+ return Object . keys ( properties ) . length > 0 ? { properties } : undefined ;
743+ } ,
657744
658745 get isInvalidKeyOrValue ( ) {
659746 return this . strategy === 'TOPIC' && this . userInput . length > 0 && ! this . keyOrValue ;
0 commit comments