forked from KaotoIO/kaoto-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(metadata): Metadata configuration UI
Fixes: KaotoIO#1722
- Loading branch information
1 parent
f0a988f
commit 4400fb2
Showing
16 changed files
with
1,052 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import { | ||
Button, | ||
FormGroup, | ||
HelperText, | ||
HelperTextItem, | ||
Popover, | ||
Radio, | ||
Split, | ||
SplitItem, | ||
Stack, | ||
StackItem, | ||
TextInput, | ||
Title, | ||
Tooltip, | ||
} from '@patternfly/react-core'; | ||
import { PlusCircleIcon } from '@patternfly/react-icons'; | ||
import { useState } from 'react'; | ||
|
||
type AddPropertyPopoverProps = { | ||
textLabel?: string; | ||
model: any; | ||
path: string[]; | ||
disabled?: boolean; | ||
onChangeModel: (model: any) => void; | ||
}; | ||
|
||
/** | ||
* Add property button shows a popover to receive user inputs for new property name and type, | ||
* as well as to validate if the property name already exists before actually adding it | ||
* into the model object. | ||
* @param props | ||
* @constructor | ||
*/ | ||
export function AddPropertyButton({ | ||
textLabel = '', | ||
model, | ||
path, | ||
disabled = false, | ||
onChangeModel, | ||
}: AddPropertyPopoverProps) { | ||
const [isVisible, doSetVisible] = useState<boolean>(false); | ||
const [propertyType, setPropertyType] = useState<'string' | 'object'>('string'); | ||
const [propertyName, setPropertyName] = useState<string>(''); | ||
const [propertyValue, setPropertyValue] = useState<string>(''); | ||
|
||
function isReadyToAdd() { | ||
return !!(propertyName && model[propertyName] == null); | ||
} | ||
|
||
function isDuplicate() { | ||
if (!model || !propertyName) { | ||
return false; | ||
} | ||
return model[propertyName] != null; | ||
} | ||
function setVisible(visible: boolean) { | ||
if (!model) { | ||
onChangeModel({}); | ||
} | ||
doSetVisible(visible); | ||
} | ||
|
||
function handleAddProperty() { | ||
if (propertyType === 'object') { | ||
model[propertyName] = {}; | ||
} else { | ||
model[propertyName] = propertyValue; | ||
} | ||
onChangeModel(model); | ||
setPropertyName(''); | ||
setPropertyValue(''); | ||
setPropertyType('string'); | ||
setVisible(false); | ||
} | ||
|
||
return ( | ||
<Popover | ||
aria-label={`add-property-popover-${path.join('-')}`} | ||
data-testid={`properties-add-property-${path.join('-')}-popover`} | ||
isVisible={isVisible} | ||
shouldOpen={() => setVisible(true)} | ||
shouldClose={() => setVisible(false)} | ||
bodyContent={ | ||
<Stack hasGutter> | ||
<StackItem> | ||
<FormGroup> | ||
<Title headingLevel="h4">Name</Title> | ||
<TextInput | ||
name="property-name" | ||
aria-label={`properties-add-property-${path.join('-')}-name-input`} | ||
data-testid={`properties-add-property-${path.join('-')}-name-input`} | ||
aria-invalid={isDuplicate()} | ||
value={propertyName} | ||
onChange={(value) => setPropertyName(value)} | ||
/> | ||
{isDuplicate() && ( | ||
<HelperText> | ||
<HelperTextItem variant="error"> | ||
Please specify a unique property name | ||
</HelperTextItem> | ||
</HelperText> | ||
)} | ||
</FormGroup> | ||
</StackItem> | ||
<StackItem> | ||
<Split hasGutter> | ||
<SplitItem> | ||
<FormGroup isInline> | ||
<Split hasGutter> | ||
<SplitItem> | ||
<Radio | ||
name="property-type" | ||
label="String" | ||
id={`properties-add-property-${path.join('-')}-type-string`} | ||
data-testid={`properties-add-property-${path.join('-')}-type-string`} | ||
isChecked={propertyType === 'string'} | ||
onChange={(checked) => checked && setPropertyType('string')} | ||
/> | ||
</SplitItem> | ||
<SplitItem> | ||
<Radio | ||
name="property-type" | ||
label="Object" | ||
id={`properties-add-property-${path.join('-')}-type-object`} | ||
data-testid={`properties-add-property-${path.join('-')}-type-object`} | ||
isChecked={propertyType === 'object'} | ||
onChange={(checked) => checked && setPropertyType('object')} | ||
/> | ||
</SplitItem> | ||
</Split> | ||
</FormGroup> | ||
</SplitItem> | ||
</Split> | ||
</StackItem> | ||
<StackItem> | ||
<FormGroup> | ||
<Title headingLevel={'h4'}>Value</Title> | ||
<TextInput | ||
name="property-value" | ||
aria-label={`properties-add-property-${path.join('-')}-value-input`} | ||
data-testid={`properties-add-property-${path.join('-')}-value-input`} | ||
value={propertyValue} | ||
isDisabled={propertyType === 'object'} | ||
onChange={(value) => setPropertyValue(value)} | ||
/> | ||
</FormGroup> | ||
</StackItem> | ||
<StackItem> | ||
<Button | ||
aria-label={`properties-add-property-${path.join('-')}-add-btn`} | ||
data-testid={`properties-add-property-${path.join('-')}-add-btn`} | ||
variant="primary" | ||
onClick={handleAddProperty} | ||
isDisabled={!isReadyToAdd()} | ||
> | ||
Add | ||
</Button> | ||
</StackItem> | ||
</Stack> | ||
} | ||
> | ||
<Tooltip content="Add property"> | ||
<Button | ||
data-testid={`properties-add-property-${path.join('-')}-popover-btn`} | ||
variant={'link'} | ||
icon={<PlusCircleIcon />} | ||
isDisabled={disabled} | ||
> | ||
{textLabel} | ||
</Button> | ||
</Tooltip> | ||
</Popover> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import PropertiesField from './PropertiesField'; | ||
import JSONSchemaBridge from 'uniforms-bridge-json-schema'; | ||
|
||
/** | ||
* Add {@link PropertiesField} custom field for adding generic properties editor. | ||
*/ | ||
export class MetadataEditorBridge extends JSONSchemaBridge { | ||
getField(name: string): Record<string, any> { | ||
const field = super.getField(name); | ||
if (field.type === 'object' && !field.properties) { | ||
field.uniforms = { | ||
component: PropertiesField, | ||
}; | ||
} | ||
return field; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
.metadataEditorModal { | ||
--pf-c-modal-box--ZIndex: 500; | ||
--pf-c-modal-box--Height: 90vh; | ||
--pf-c-modal-box--Width: 90vw; | ||
--pf-c-modal-box__body--MinHeight: 90vh; | ||
--pf-c-modal-box__body--MaxHeight: 90vh; | ||
--pf-c-modal-box__body--MinWidth: 90vw; | ||
--pf-c-modal-box__body--MaxWidth: 90vw; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { MetadataEditorModal } from './MetadataEditorModal'; | ||
import { useArgs } from '@storybook/client-api'; | ||
import { StoryFn, Meta } from '@storybook/react'; | ||
|
||
export default { | ||
title: 'Metadata/MetadataEditorModal', | ||
component: MetadataEditorModal, | ||
excludeStories: ['schemaMock'], | ||
decorators: [ | ||
(Story) => ( | ||
<div style={{ margin: '3em' }}> | ||
<Story /> | ||
</div> | ||
), | ||
], | ||
argTypes: { handleCloseModal: { action: 'clicked' } }, | ||
} as Meta<typeof MetadataEditorModal>; | ||
|
||
const Template: StoryFn<typeof MetadataEditorModal> = (args) => { | ||
const [{ isModalOpen }, updateArgs] = useArgs(); | ||
const handleClose = () => updateArgs({ isModalOpen: !isModalOpen }); | ||
return ( | ||
<> | ||
<button onClick={() => updateArgs({ isModalOpen: !isModalOpen })}> | ||
Open Metadata Editor Modal | ||
</button> | ||
<MetadataEditorModal {...args} handleCloseModal={handleClose} /> | ||
</> | ||
); | ||
}; | ||
|
||
export const schemaMock = { | ||
beans: { | ||
title: 'Beans', | ||
description: 'Beans Configuration', | ||
type: 'array', | ||
items: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
name: { | ||
type: 'string', | ||
}, | ||
type: { | ||
type: 'string', | ||
}, | ||
properties: { | ||
type: 'object', | ||
}, | ||
}, | ||
required: ['name', 'type'], | ||
}, | ||
}, | ||
single: { | ||
title: 'Single Object', | ||
description: 'Single Object Configuration', | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { | ||
name: { | ||
type: 'string', | ||
}, | ||
type: { | ||
type: 'string', | ||
}, | ||
properties: { | ||
type: 'object', | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
export const BeansArray = Template.bind({}); | ||
BeansArray.args = { | ||
name: 'beans', | ||
schema: schemaMock.beans, | ||
}; | ||
|
||
export const SingleObject = Template.bind({}); | ||
SingleObject.args = { | ||
name: 'singleObject', | ||
schema: schemaMock.single, | ||
}; |
Oops, something went wrong.