diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 50049f5cb..5ed4a9874 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -104,9 +104,17 @@ value.template=Template value.string=String value.boolean=Boolean value.list=List +value.long=Long +value.double=Double +value.duration=Duration +value.spring-bean-id=Spring Bean ID value.BOOLEAN=Boolean value.SELECTION_LIST=List value.STRING=String +value.LONG=Long +value.DOUBLE=Double +value.DURATION=Duration +value.SPRING_BEAN_ID=Spring Bean ID brand.header.title=Source Management brand.logo-link-label=Shibboleth @@ -140,11 +148,23 @@ label.entity-attribute-default=Default Value tooltip.entity-attribute-default=Default Value label.entity-attribute-list-options=List options tooltip.entity-attribute-list-options=List options +label.entity-attribute-friendly-name=Friendly name +tooltip.entity-attribute-friendly-name=Friendly name +label.entity-attribute-attr-name=Attribute name +tooltip.entity-attribute-attr-name=This is normally a uri or urn +label.entity-attribute-display-name=Display name +tooltip.entity-attribute-display-name=Display name + +label.entity-attribute-persist-friendly-name=Persist Friendly Name +label.entity-attribute-persist-type=Persist Type +tooltip.entity-attribute-persist-friendly-name=Persist Friendly Name +tooltip.entity-attribute-persist-type=Persist Type label.entity-attributes=Entity Attributes label.custom-entity-attributes=Custom Entity Attributes label.help-text=Help text label.default-value=Default Value +label.new-attribute=New Custom Entity Attribute label.metadata-source=Metadata Source label.metadata-sources=Metadata Sources diff --git a/ui/public/assets/schema/attribute/attribute.schema.json b/ui/public/assets/schema/attribute/attribute.schema.json index cc99dc3a6..bb731f20b 100644 --- a/ui/public/assets/schema/attribute/attribute.schema.json +++ b/ui/public/assets/schema/attribute/attribute.schema.json @@ -2,7 +2,10 @@ "type": "object", "required": [ "name", - "attributeType" + "attributeType", + "attributeFriendlyName", + "attributeName", + "displayName" ], "properties": { "name": { @@ -19,14 +22,67 @@ "enum": [ "STRING", "BOOLEAN", - "SELECTION_LIST" + "SELECTION_LIST", + "LONG", + "DOUBLE", + "DURATION", + "SPRING_BEAN_ID" ], "enumNames": [ "value.string", "value.boolean", - "value.list" + "value.list", + "value.long", + "value.double", + "value.duration", + "value.spring-bean-id" ] }, + "attributeFriendlyName": { + "type": "string", + "title": "label.entity-attribute-friendly-name", + "description": "tooltip.entity-attribute-friendly-name", + "minLength": 1, + "maxLength": 255 + }, + "attributeName": { + "type": "string", + "title": "label.entity-attribute-attr-name", + "description": "tooltip.entity-attribute-attr-name", + "minLength": 1, + "maxLength": 255 + }, + "persistFriendlyName": { + "type": "string", + "title": "label.entity-attribute-persist-friendly-name", + "description": "tooltip.entity-attribute-persist-friendly-name", + "minLength": 1, + "maxLength": 255 + }, + "persistType": { + "type": "string", + "title": "label.entity-attribute-persist-type", + "description": "tooltip.entity-attribute-persist-type", + "minLength": 1, + "maxLength": 255, + "enum": [ + "boolean", + "string", + "number" + ], + "enumNames": [ + "boolean", + "string", + "number" + ] + }, + "displayName": { + "type": "string", + "title": "label.entity-attribute-display-name", + "description": "tooltip.entity-attribute-display-name", + "minLength": 1, + "maxLength": 255 + }, "helpText": { "title": "label.entity-attribute-help", "description": "tooltip.entity-attribute-help", @@ -45,7 +101,7 @@ "STRING" ] }, - "defaultValueString": { + "defaultValue": { "title": "label.entity-attribute-default", "description": "tooltip.entity-attribute-default", "type": "string" @@ -59,12 +115,73 @@ "BOOLEAN" ] }, - "defaultValueBoolean": { + "defaultValue": { + "title": "label.entity-attribute-default", + "description": "tooltip.entity-attribute-default", + "type": "string", + "default": "true", + "enumNames": [ + "True", + "False" + ] + } + } + }, + { + "properties": { + "attributeType": { + "enum": [ + "DURATION" + ] + }, + "defaultValue": { + "title": "label.entity-attribute-default", + "description": "tooltip.entity-attribute-default", + "type": "string", + "pattern": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$" + } + } + }, + { + "properties": { + "attributeType": { + "enum": [ + "LONG" + ] + }, + "defaultValue": { + "title": "label.entity-attribute-default", + "description": "tooltip.entity-attribute-default", + "type": "string" + } + } + }, + { + "properties": { + "attributeType": { + "enum": [ + "DOUBLE" + ] + }, + "defaultValue": { + "title": "label.entity-attribute-default", + "description": "tooltip.entity-attribute-default", + "type": "string" + } + } + }, + { + "properties": { + "attributeType": { + "enum": [ + "INTEGER" + ] + }, + "defaultValue": { "title": "label.entity-attribute-default", "description": "tooltip.entity-attribute-default", - "type": "boolean", - "default": true, - "enumNames": ["True", "False"] + "type": "string", + "pattern": "^\\d+$" } } }, diff --git a/ui/src/app/form/component/fields/FilterTargetField.js b/ui/src/app/form/component/fields/FilterTargetField.js index bad90052e..2bfd8484d 100644 --- a/ui/src/app/form/component/fields/FilterTargetField.js +++ b/ui/src/app/form/component/fields/FilterTargetField.js @@ -74,7 +74,7 @@ const FilterTargetField = ({ /*eslint-disable react-hooks/exhaustive-deps*/ React.useEffect(() => { - if (term && term.length >= 4) { + if (term?.length >= 4) { searchIds(term); } }, [term]); diff --git a/ui/src/app/form/component/templates/FieldTemplate.js b/ui/src/app/form/component/templates/FieldTemplate.js index 1e2b8f7eb..1203d543a 100644 --- a/ui/src/app/form/component/templates/FieldTemplate.js +++ b/ui/src/app/form/component/templates/FieldTemplate.js @@ -24,8 +24,8 @@ export function FieldTemplate ({ {children}
- {rawHelp && rawErrors.length < 1 && ( - 0 ? "text-danger" : "text-muted"} id={id}> + {rawHelp && rawErrors?.length < 1 && ( + 0 ? "text-danger" : "text-muted"} id={id}> )} diff --git a/ui/src/app/form/component/widgets/OptionWidget.js b/ui/src/app/form/component/widgets/OptionWidget.js index 88648c0c5..534595ead 100644 --- a/ui/src/app/form/component/widgets/OptionWidget.js +++ b/ui/src/app/form/component/widgets/OptionWidget.js @@ -80,7 +80,7 @@ const OptionWidget = ({ } }; - const defaultInputValue = typeof value === 'object' && value && value.label ? value.label : value; + const defaultInputValue = typeof value === 'object' && value && value.label ? value.label : value ? value : ''; const [ inputValue, setInputValue ] = React.useState( defaultInputValue ); @@ -90,7 +90,7 @@ const OptionWidget = ({ return ( - 0) ? "text-danger" : ""}`}> + 0) ? "text-danger" : ""}`}> {(label || schema.title) && required ? : null} @@ -104,7 +104,7 @@ const OptionWidget = ({ onChange={ _onChange } allowNew={true} multiple={false} - className={`toggle-typeahead ${rawErrors.length > 0 ? "is-invalid" : ""}`} + className={`toggle-typeahead ${rawErrors?.length > 0 ? "is-invalid" : ""}`} options={opts} placeholder={uiSchema['ui:placeholder'] ? translator(uiSchema['ui:placeholder'] ): ''} disabled={disabled || readonly} @@ -122,7 +122,7 @@ const OptionWidget = ({ toggleMenu()} disabled={disabled || readonly} /> )} - {rawErrors.length > 0 && touched && ( + {rawErrors?.length > 0 && touched && ( {rawErrors.map((error, i) => { return ( diff --git a/ui/src/app/form/component/widgets/SelectWidget.js b/ui/src/app/form/component/widgets/SelectWidget.js index 9a2cd421c..76dc99ec6 100644 --- a/ui/src/app/form/component/widgets/SelectWidget.js +++ b/ui/src/app/form/component/widgets/SelectWidget.js @@ -86,7 +86,7 @@ const SelectWidget = ({ return ( - 0 ? "text-danger" : ""}`}> + 0 ? "text-danger" : ""}`}> {(label || schema.title) && required ? : null} @@ -103,7 +103,7 @@ const SelectWidget = ({ disabled={disabled} readOnly={readonly} autoFocus={autofocus} - className={touched && rawErrors.length > 0 ? "is-invalid" : ""} + className={touched && rawErrors?.length > 0 ? "is-invalid" : ""} onBlur={ onBlur && ((event) => { @@ -132,7 +132,7 @@ const SelectWidget = ({ )} - {rawErrors.length > 0 && touched && ( + {rawErrors?.length > 0 && touched && ( {rawErrors.map((error, i) => { return ( diff --git a/ui/src/app/form/component/widgets/TextWidget.js b/ui/src/app/form/component/widgets/TextWidget.js index 08343905f..8554be911 100644 --- a/ui/src/app/form/component/widgets/TextWidget.js +++ b/ui/src/app/form/component/widgets/TextWidget.js @@ -38,10 +38,10 @@ const TextWidget = ({ _onBlur(evt); }; - // const classNames = [rawErrors.length > 0 ? "is-invalid" : "", type === 'file' ? 'custom-file-label': ""] + // const classNames = [rawErrors?.length > 0 ? "is-invalid" : "", type === 'file' ? 'custom-file-label': ""] return ( - 0 && touched ? "text-danger" : ""}`}> + 0 && touched ? "text-danger" : ""}`}> {(label || schema.title) && required ? @@ -56,7 +56,7 @@ const TextWidget = ({ required={required} disabled={disabled} readOnly={readonly} - className={rawErrors.length > 0 && touched ? "is-invalid" : ""} + className={rawErrors?.length > 0 && touched ? "is-invalid" : ""} list={schema.examples ? `examples_${id}` : undefined} type={inputType} value={value || value === 0 ? value : ""} @@ -73,7 +73,7 @@ const TextWidget = ({ })} ) : null} - {rawErrors.length > 0 && touched && ( + {rawErrors?.length > 0 && touched && ( {rawErrors.map((error, i) => { return ( diff --git a/ui/src/app/form/component/widgets/TextareaWidget.js b/ui/src/app/form/component/widgets/TextareaWidget.js index 9afdace93..4dad923ef 100644 --- a/ui/src/app/form/component/widgets/TextareaWidget.js +++ b/ui/src/app/form/component/widgets/TextareaWidget.js @@ -46,7 +46,7 @@ const TextareaWidget = ({ return ( <> - 0 ? "text-danger" : ""}`}> + 0 ? "text-danger" : ""}`}> {(label || schema.title) && required ? : null} @@ -69,7 +69,7 @@ const TextareaWidget = ({ onFocus={_onFocus} /> - {rawErrors.length > 0 && touched && ( + {rawErrors?.length > 0 && touched && ( {rawErrors.map((error, i) => { return ( diff --git a/ui/src/app/form/component/widgets/UpDownWidget.js b/ui/src/app/form/component/widgets/UpDownWidget.js index 4394a070f..9a6941852 100644 --- a/ui/src/app/form/component/widgets/UpDownWidget.js +++ b/ui/src/app/form/component/widgets/UpDownWidget.js @@ -66,7 +66,7 @@ const UpDownWidget = ({ onBlur={onCustomBlur} onFocus={_onFocus} /> - {rawErrors.length > 0 && touched && ( + {rawErrors?.length > 0 && touched && ( {rawErrors.map((error, i) => { return ( diff --git a/ui/src/app/metadata/domain/attribute/CustomAttributeDefinition.js b/ui/src/app/metadata/domain/attribute/CustomAttributeDefinition.js index d04cfab6e..833a3962d 100644 --- a/ui/src/app/metadata/domain/attribute/CustomAttributeDefinition.js +++ b/ui/src/app/metadata/domain/attribute/CustomAttributeDefinition.js @@ -15,6 +15,11 @@ export const CustomAttributeDefinition = { fields: [ 'name', 'attributeType', + 'attributeName', + 'attributeFriendlyName', + 'displayName', + 'persistFriendlyName', + 'persistType', 'helpText' ] }, diff --git a/ui/src/app/metadata/editor/MetadataAttributeEditor.js b/ui/src/app/metadata/editor/MetadataAttributeEditor.js index 9c13e2a5f..126444729 100644 --- a/ui/src/app/metadata/editor/MetadataAttributeEditor.js +++ b/ui/src/app/metadata/editor/MetadataAttributeEditor.js @@ -1,6 +1,7 @@ import React from 'react'; import { MetadataFormContext, setFormDataAction, setFormErrorAction } from '../hoc/MetadataFormContext'; import { MetadataDefinitionContext, MetadataSchemaContext } from '../hoc/MetadataSchema'; +import { transformErrors } from '../domain/transform'; import Form from '@rjsf/bootstrap-4'; @@ -44,7 +45,8 @@ export function MetadataAttributeEditor({ children }) { fields={fields} widgets={widgets} liveValidate={true} - ErrorList={ErrorListTemplate}> + ErrorList={ErrorListTemplate} + transformErrors={transformErrors}> <>