From 95d56d62da797cbd05a311c7e1cf6c28d866f791 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 14 May 2021 14:33:18 -0700 Subject: [PATCH] Implemented wizard for provider/source --- ui/package.json | 1 - .../provider/filebacked-http.schema.json | 27 +++- ui/src/app/dashboard/view/ProvidersTab.js | 4 +- .../form/component/templates/FieldTemplate.js | 4 +- .../templates/ObjectFieldTemplate.js | 2 +- ui/src/app/metadata/NewProvider.js | 19 ++- ui/src/app/metadata/NewSource.js | 2 +- ui/src/app/metadata/domain/index.js | 7 +- .../domain/provider/BaseProviderDefinition.js | 46 ++++++- .../DynamicHttpMetadataProviderDefinition.js | 51 +++----- ...ileBackedHttpMetadataProviderDefinition.js | 20 +-- .../FileSystemMetadataProviderDefinition.js | 45 ++----- .../LocalDynamicMetadataProviderDefinition.js | 44 ++----- .../utility/providerFilterProcessor.js | 3 +- .../domain/source/SourceDefinition.js | 77 ++++-------- ui/src/app/metadata/editor/MetadataEditor.js | 21 ++-- .../app/metadata/editor/MetadataEditorForm.js | 5 +- .../app/metadata/hoc/MetadataFormContext.js | 17 ++- ui/src/app/metadata/hooks/api.js | 12 +- ui/src/app/metadata/hooks/configuration.js | 4 +- .../app/metadata/view/MetadataComparison.js | 5 +- ui/src/app/metadata/view/MetadataOptions.js | 4 +- ui/src/app/metadata/view/MetadataUpload.js | 2 +- ui/src/app/metadata/view/MetadataVersion.js | 5 +- ui/src/app/metadata/view/MetadataWizard.js | 8 +- .../metadata/wizard/MetadataProviderWizard.js | 94 +++++++++++++- .../metadata/wizard/MetadataSchemaSelector.js | 116 ++++++++++++++++++ .../metadata/wizard/MetadataSourceWizard.js | 16 +-- .../app/metadata/wizard/MetadataWizardForm.js | 5 +- ui/src/app/metadata/wizard/Wizard.js | 6 + ui/src/app/metadata/wizard/WizardNav.js | 14 ++- 31 files changed, 462 insertions(+), 224 deletions(-) create mode 100644 ui/src/app/metadata/wizard/MetadataSchemaSelector.js diff --git a/ui/package.json b/ui/package.json index 2964e049d..af5b3120b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -26,7 +26,6 @@ "react-dom": "^17.0.2", "react-hook-form": "^7.5.2", "react-infinite-scroll-component": "^6.1.0", - "react-jsonschema-form-layout-grid": "^2.1.0", "react-router-dom": "^5.2.0", "react-scripts": "4.0.3", "react-scroll": "^1.8.2", diff --git a/ui/public/assets/schema/provider/filebacked-http.schema.json b/ui/public/assets/schema/provider/filebacked-http.schema.json index 5c921eec6..5940a8794 100644 --- a/ui/public/assets/schema/provider/filebacked-http.schema.json +++ b/ui/public/assets/schema/provider/filebacked-http.schema.json @@ -342,6 +342,10 @@ "id": "fieldset" }, "properties": { + "@type": { + "type": "string", + "default": "RequiredValidUntil" + }, "maxValidityInterval": { "title": "label.max-validity-interval", "description": "tooltip.max-validity-interval", @@ -358,6 +362,10 @@ "id": "fieldset" }, "properties": { + "@type": { + "type": "string", + "default": "SignatureValidation" + }, "requireSignedRoot": { "title": "label.require-signed-root", "description": "tooltip.require-signed-root", @@ -401,14 +409,27 @@ "id": "fieldset" }, "properties": { + "@type": { + "type": "string", + "default": "EntityRoleWhiteList" + }, "retainedRoles": { "title": "label.retained-roles", "description": "tooltip.retained-roles", "type": "array", "items": { - "enum": ["SPSSODescriptor", "AttributeAuthorityDescriptor"], - "enumNames": ["value.spdescriptor", "value.attr-auth-descriptor"], - "type": "string" + "widget": { + "id": "select" + }, + "type": "string", + "enum": [ + "SPSSODescriptor", + "AttributeAuthorityDescriptor" + ], + "enumNames": [ + "value.spdescriptor", + "value.attr-auth-descriptor" + ] } }, "removeRolelessEntityDescriptors": { diff --git a/ui/src/app/dashboard/view/ProvidersTab.js b/ui/src/app/dashboard/view/ProvidersTab.js index 1bf1c4629..123e54f41 100644 --- a/ui/src/app/dashboard/view/ProvidersTab.js +++ b/ui/src/app/dashboard/view/ProvidersTab.js @@ -14,7 +14,9 @@ export function ProvidersTab () { const [providers, setProviders] = React.useState([]); - const { get, response } = useMetadataEntities('provider'); + const { get, response } = useMetadataEntities('provider', { + cachePolicy: 'no-cache' + }); async function loadProviders() { const providers = await get('') diff --git a/ui/src/app/form/component/templates/FieldTemplate.js b/ui/src/app/form/component/templates/FieldTemplate.js index 2e196376f..fea2b4831 100644 --- a/ui/src/app/form/component/templates/FieldTemplate.js +++ b/ui/src/app/form/component/templates/FieldTemplate.js @@ -31,13 +31,13 @@ export function FieldTemplate ({ return ( 0 ? 'sr-only' : ''}`}> - {error} + {error} ); })} - )} + )} {rawHelp && rawErrors.length < 1 && ( 0 ? "text-danger" : "text-muted"} id={id}> diff --git a/ui/src/app/form/component/templates/ObjectFieldTemplate.js b/ui/src/app/form/component/templates/ObjectFieldTemplate.js index c925802f4..0fe0089e4 100644 --- a/ui/src/app/form/component/templates/ObjectFieldTemplate.js +++ b/ui/src/app/form/component/templates/ObjectFieldTemplate.js @@ -16,7 +16,7 @@ const ObjectFieldTemplate = ({ idSchema, schema, hidden, - formContext + ...props }) => { const displayTitle = (uiSchema["ui:title"] || (title && schema.title)); diff --git a/ui/src/app/metadata/NewProvider.js b/ui/src/app/metadata/NewProvider.js index 3cf143726..75a521e01 100644 --- a/ui/src/app/metadata/NewProvider.js +++ b/ui/src/app/metadata/NewProvider.js @@ -1,10 +1,14 @@ import React from 'react'; import Translate from '../i18n/components/translate'; import { MetadataSchema } from './hoc/MetadataSchema'; +import { useMetadataProviderTypes } from './hooks/api'; import { MetadataWizard } from './view/MetadataWizard'; +import { MetadataSchemaSelector } from './wizard/MetadataSchemaSelector'; export function NewProvider() { + const { data } = useMetadataProviderTypes({}, []); + return (
@@ -16,9 +20,18 @@ export function NewProvider() {
- - - + + {(data, onRestart) => + + + + } +
diff --git a/ui/src/app/metadata/NewSource.js b/ui/src/app/metadata/NewSource.js index 146d041b7..7139a453c 100644 --- a/ui/src/app/metadata/NewSource.js +++ b/ui/src/app/metadata/NewSource.js @@ -78,7 +78,7 @@ export function NewSource() { - { setShowNav(s) }} /> + { setShowNav(s) }} /> } /> diff --git a/ui/src/app/metadata/domain/index.js b/ui/src/app/metadata/domain/index.js index 16d176953..70edb596c 100644 --- a/ui/src/app/metadata/domain/index.js +++ b/ui/src/app/metadata/domain/index.js @@ -1,5 +1,5 @@ import { MetadataFilterEditorTypes } from './filter'; -import { MetadataProviderEditorTypes } from './provider'; +import { MetadataProviderEditorTypes, MetadataProviderWizardTypes } from './provider'; import { SourceEditor, SourceWizard } from "./source/SourceDefinition"; export const editors = { @@ -13,12 +13,15 @@ export const wizards = { export const ProviderEditorTypes = [ ...MetadataProviderEditorTypes ]; +export const ProviderWizardTypes = [ + ...MetadataProviderWizardTypes +]; export const FilterEditorTypes = [ ...MetadataFilterEditorTypes ]; export const getWizard = (type) => - ProviderEditorTypes.find(def => def.type === type) || + ProviderWizardTypes.find(def => def.type === type) || FilterEditorTypes.find(def => def.type === type) || SourceWizard; diff --git a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js index 3148c1683..6fe346727 100644 --- a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js @@ -3,6 +3,22 @@ import { DurationOptions } from '../data'; export const BaseProviderDefinition = { schemaPreprocessor: metadataFilterProcessor, + validator: (data = [], current = { resourceId: null }) => { + const providers = data.filter(p => p.resourceId !== current.resourceId); + const names = providers.map(s => s.name); + const ids = providers.map(s => s.xmlId); + + return (formData, errors) => { + if (names.indexOf(formData.name) > -1) { + errors.name.addError('message.name-must-be-unique'); + } + + if (ids.indexOf(formData.xmlId) > -1) { + errors.xmlId.addError('message.id-unique'); + } + return errors; + } + }, parser: (changes) => { return (changes.metadataFilters ? ({ ...changes, @@ -14,9 +30,8 @@ export const BaseProviderDefinition = { }) : changes) }, formatter: (changes, schema) => { - const filterSchema = schema?.properties?.metadataFilters; - if (!filterSchema) { + if (!filterSchema || !changes) { return changes; } @@ -33,14 +48,13 @@ export const BaseProviderDefinition = { return formatted; }, display: (changes) => { - if (!changes.metadataFilters) { return changes; } return { ...changes, metadataFilters: { - ...(changes.metadataFilters || []).reduce((collection, filter) => ({ + ...changes.metadataFilters.reduce((collection, filter) => ({ ...collection, [filter['@type']]: filter }), {}) @@ -51,7 +65,29 @@ export const BaseProviderDefinition = { name: { 'ui:help': 'message.must-be-unique' } - } + }, + steps: [ + { + id: 'new', + label: 'label.select-metadata-provider-type', + index: 1, + initialValues: [], + fields: [ + 'name', + '@type' + ], + fieldsets: [ + { + type: 'section', + class: ['col-12'], + fields: [ + 'name', + '@type' + ] + } + ] + } + ] } export const HttpMetadataResolverAttributesSchema = { diff --git a/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js index e22122e6b..a8f304121 100644 --- a/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js @@ -12,6 +12,7 @@ export const DynamicHttpMetadataProviderWizard = { schema: '/assets/schema/provider/dynamic-http.schema.json', // schema: `${API_BASE_PATH}/ui/MetadataResolver/DynamicHttpMetadataResolver`, steps: [ + ...BaseProviderDefinition.steps, { id: 'common', label: 'label.common-attributes', @@ -56,16 +57,15 @@ export const DynamicHttpMetadataProviderWizard = { layout: { groups: [ { - size: 9, + size: 8, classNames: 'bg-light border rounded px-4 pt-4 pb-3 mb-4', fields: [ 'name', - '@type', - 'enabled' + '@type' ] }, { - size: 9, + size: 8, fields: [ 'xmlId', 'requireValidMetadata', @@ -74,22 +74,28 @@ export const DynamicHttpMetadataProviderWizard = { ] }, { - size: 9, + size: 8, fields: [ 'dynamicMetadataResolverAttributes' ], }, { - size: 9, + size: 8, fields: [ 'metadataFilters' ], }, { - size: 9, + size: 8, fields: [ 'httpMetadataResolverAttributes' ] + }, + { + size: 8, + fields: [ + 'enabled' + ] } ] }, @@ -172,36 +178,7 @@ export const DynamicHttpMetadataProviderEditor = { 'enabled', 'requireValidMetadata', 'failFastInitialization' - ], - fieldsets: [ - { - type: 'section', - class: ['mb-3'], - fields: [ - 'name', - '@type' - ] - }, - { - type: 'group-lg', - class: ['col-12'], - fields: [ - 'xmlId', - 'metadataRequestURLConstructionScheme', - 'enabled', - 'requireValidMetadata', - 'failFastInitialization' - ] - } - ], - override: { - '@type': { - type: 'string', - readOnly: true, - widget: 'string', - oneOf: [{ enum: ['DynamicHttpMetadataResolver'], description: 'value.dynamic-http-metadata-provider' }] - } - } + ] }, { id: 'dynamic', diff --git a/ui/src/app/metadata/domain/provider/FileBackedHttpMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/FileBackedHttpMetadataProviderDefinition.js index d7faad4d7..fb4d3818d 100644 --- a/ui/src/app/metadata/domain/provider/FileBackedHttpMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/FileBackedHttpMetadataProviderDefinition.js @@ -9,6 +9,7 @@ export const FileBackedHttpMetadataProviderWizard = { type: 'FileBackedHttpMetadataResolver', schema: '/assets/schema/provider/filebacked-http.schema.json', steps: [ + ...BaseProviderDefinition.steps, { id: 'common', label: 'label.common-attributes', @@ -60,16 +61,15 @@ export const FileBackedHttpMetadataProviderWizard = { layout: { groups: [ { - size: 9, + size: 8, classNames: 'bg-light border rounded px-4 pt-4 pb-3 mb-4', fields: [ 'name', - '@type', - 'enabled' + '@type' ] }, { - size: 9, + size: 8, fields: [ 'xmlId', 'metadataURL', @@ -83,22 +83,28 @@ export const FileBackedHttpMetadataProviderWizard = { ] }, { - size: 9, + size: 8, fields: [ 'reloadableMetadataResolverAttributes' ], }, { - size: 9, + size: 8, fields: [ 'metadataFilters' ], }, { - size: 9, + size: 8, fields: [ 'httpMetadataResolverAttributes' ] + }, + { + size: 8, + fields: [ + 'enabled' + ] } ] }, diff --git a/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js index 5fb5dc68d..7c72764e0 100644 --- a/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js @@ -10,6 +10,7 @@ export const FileSystemMetadataProviderWizard = { schema: '/assets/schema/provider/file-system.schema.json', // schema: `${API_BASE_PATH}/ui/MetadataResolver/FilesystemMetadataResolver`, steps: [ + ...BaseProviderDefinition.steps, { id: 'common', label: 'label.common-attributes', @@ -19,17 +20,6 @@ export const FileSystemMetadataProviderWizard = { 'xmlId', 'metadataFile', 'doInitialization' - ], - fieldsets: [ - { - type: 'group-lg', - class: ['col-12'], - fields: [ - 'xmlId', - 'metadataFile', - 'doInitialization' - ] - } ] }, { @@ -39,15 +29,6 @@ export const FileSystemMetadataProviderWizard = { initialValues: [], fields: [ 'reloadableMetadataResolverAttributes' - ], - fieldsets: [ - { - type: 'group-lg', - class: ['col-12'], - fields: [ - 'reloadableMetadataResolverAttributes' - ] - } ] }, { @@ -57,15 +38,6 @@ export const FileSystemMetadataProviderWizard = { initialValues: [], fields: [ 'enabled' - ], - fieldsets: [ - { - type: 'group-lg', - class: ['col-12'], - fields: [ - 'enabled' - ] - } ] } ], @@ -73,16 +45,15 @@ export const FileSystemMetadataProviderWizard = { layout: { groups: [ { - size: 9, + size: 8, classNames: 'bg-light border rounded px-4 pt-4 pb-3 mb-4', fields: [ 'name', - '@type', - 'enabled' + '@type' ] }, { - size: 9, + size: 8, fields: [ 'xmlId', 'metadataFile', @@ -90,10 +61,16 @@ export const FileSystemMetadataProviderWizard = { ] }, { - size: 9, + size: 8, fields: [ 'reloadableMetadataResolverAttributes' ] + }, + { + size: 8, + fields: [ + 'enabled' + ] } ] }, diff --git a/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js index b6e3847f6..94635ddfc 100644 --- a/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js @@ -11,6 +11,7 @@ export const LocalDynamicMetadataProviderWizard = { schema: '/assets/schema/provider/local-dynamic.schema.json', // schema: `${API_BASE_PATH}/ui/MetadataResolver/LocalDynamicMetadataResolver`, steps: [ + ...BaseProviderDefinition.steps, { id: 'common', label: 'label.common-attributes', @@ -19,16 +20,6 @@ export const LocalDynamicMetadataProviderWizard = { fields: [ 'xmlId', 'sourceDirectory' - ], - fieldsets: [ - { - type: 'group-lg', - class: ['col-12'], - fields: [ - 'xmlId', - 'sourceDirectory' - ] - } ] }, { @@ -38,15 +29,6 @@ export const LocalDynamicMetadataProviderWizard = { initialValues: [], fields: [ 'dynamicMetadataResolverAttributes' - ], - fieldsets: [ - { - type: 'group-lg', - class: ['col-12'], - fields: [ - 'dynamicMetadataResolverAttributes' - ] - } ] }, { @@ -56,15 +38,6 @@ export const LocalDynamicMetadataProviderWizard = { initialValues: [], fields: [ 'enabled' - ], - fieldsets: [ - { - type: 'group-lg', - class: ['col-12'], - fields: [ - 'enabled' - ] - } ] } ], @@ -72,26 +45,31 @@ export const LocalDynamicMetadataProviderWizard = { layout: { groups: [ { - size: 9, + size: 8, classNames: 'bg-light border rounded px-4 pt-4 pb-3 mb-4', fields: [ 'name', - '@type', - 'enabled' + '@type' ] }, { - size: 9, + size: 8, fields: [ 'xmlId', 'sourceDirectory' ] }, { - size: 9, + size: 8, fields: [ 'dynamicMetadataResolverAttributes' ], + }, + { + size: 8, + fields: [ + 'enabled' + ] } ] }, diff --git a/ui/src/app/metadata/domain/provider/utility/providerFilterProcessor.js b/ui/src/app/metadata/domain/provider/utility/providerFilterProcessor.js index dc415bae1..f436b50ff 100644 --- a/ui/src/app/metadata/domain/provider/utility/providerFilterProcessor.js +++ b/ui/src/app/metadata/domain/provider/utility/providerFilterProcessor.js @@ -1,4 +1,5 @@ export const metadataFilterProcessor = (schema) => { + console.log(schema); if (!schema) { return null; } @@ -12,7 +13,7 @@ export const metadataFilterProcessor = (schema) => { ...schema.properties, metadataFilters: { type: 'object', - properties: filters.items.reduce((collection, filterType) => ({ + properties: filters?.items?.reduce((collection, filterType) => ({ ...collection, [filterType.$id]: filterType }), {}) diff --git a/ui/src/app/metadata/domain/source/SourceDefinition.js b/ui/src/app/metadata/domain/source/SourceDefinition.js index 1674e8dac..493658213 100644 --- a/ui/src/app/metadata/domain/source/SourceDefinition.js +++ b/ui/src/app/metadata/domain/source/SourceDefinition.js @@ -18,59 +18,32 @@ export const SourceBase = { display: (changes) => changes, - getValidators: (entityIdList) => { - const validators = { - '/': (value, property, form_current) => { - let errors; - // iterate all customer - Object.keys(value).forEach((key) => { - const item = value[key]; - const validatorKey = `/${key}`; - const validator = validators.hasOwnProperty(validatorKey) ? validators[validatorKey] : null; - const error = validator ? validator(item, form_current.getProperty(key), form_current) : null; - if (error && error.invalidate) { - errors = errors || []; - errors.push(error); - } - }); - return errors; - }, - '/entityId': (value, property, form) => { - const err = entityIdList.indexOf(value) > -1 ? { - code: 'INVALID_ID', - path: `#${property.path}`, - message: 'message.id-unique', - params: [value], - invalidate: true - } : null; - return err; - }, - '/relyingPartyOverrides': (value, property, form) => { - if (!value.signAssertion && value.dontSignResponse) { - return { - code: 'INVALID_SIGNING', - path: `#${property.path}`, - message: 'message.invalid-signing', - params: [value], - invalidate: false - }; - } - return null; - }, - '/serviceProviderSsoDescriptor': (value, property, form) => { - if (value.nameIdFormats && value.nameIdFormats.length && !value.protocolSupportEnum) { - return { - code: 'PROTOCOL_SUPPORT_ENUM_REQUIRED', - path: `#${property.path}`, - message: 'message.protocol-support-required', - params: [value], - invalidate: true - }; - } - return null; + validator: (data = [], current = {id: null}) => { + + const sources = current ? data.filter(s => s.id !== current.id) : data; + const entityIds = sources.map(s => s.entityId); + + console.log(sources); + + return (formData, errors) => { + console.log(formData) + if (entityIds.indexOf(formData.entityId) > -1) { + errors.entityId.addError('message.id-unique'); } - }; - return validators; + + /*if (!formData?.relyingPartyOverrides?.signAssertion && formData?.relyingPartyOverrides?.dontSignResponse) { + errors = { + ...errors, + relyingPartyOverrides: { + dontSignResponse: { + __errors: ['message.invalid-signing'], + nonBlocking: true + } + } + }; + }*/ + return errors; + } }, uiSchema: { 'ui:order': ['serviceProviderName', '*'], diff --git a/ui/src/app/metadata/editor/MetadataEditor.js b/ui/src/app/metadata/editor/MetadataEditor.js index 4e4e219b2..5792e74ee 100644 --- a/ui/src/app/metadata/editor/MetadataEditor.js +++ b/ui/src/app/metadata/editor/MetadataEditor.js @@ -10,24 +10,23 @@ import { MetadataDefinitionContext, MetadataSchemaContext } from '../hoc/Metadat import { MetadataEditorForm } from './MetadataEditorForm'; import { MetadataEditorNav } from './MetadataEditorNav'; -import { useMetadataEntity } from '../hooks/api'; +import { useMetadataEntities, useMetadataEntity } from '../hooks/api'; +import { MetadataObjectContext } from '../hoc/MetadataSelector'; export function MetadataEditor () { const { type, id, section } = useParams(); - const { put, response } = useMetadataEntity(type, {}, []); + const { put, response, saving } = useMetadataEntity(type, {}, []); - + const { data } = useMetadataEntities(type, {}, []); const history = useHistory(); const definition = React.useContext(MetadataDefinitionContext); const schema = React.useContext(MetadataSchemaContext); - - const [invalid] = React.useState(false); - const [saving] = React.useState(false); + const current = React.useContext(MetadataObjectContext); const { state, dispatch } = React.useContext(MetadataFormContext); - const { metadata, errors} = state; + const { metadata, errors } = state; const onChange = (changes) => { dispatch(setFormDataAction(changes.formData)); @@ -57,6 +56,9 @@ export function MetadataEditor () { const [blocking, setBlocking] = React.useState(false); + const validator = definition.validator(data, current); + + // console.log(errors); return (
@@ -93,7 +95,7 @@ export function MetadataEditor () {
diff --git a/ui/src/app/metadata/editor/MetadataEditorForm.js b/ui/src/app/metadata/editor/MetadataEditorForm.js index 0f948ec1b..6413cbedf 100644 --- a/ui/src/app/metadata/editor/MetadataEditorForm.js +++ b/ui/src/app/metadata/editor/MetadataEditorForm.js @@ -14,7 +14,7 @@ function ErrorListTemplate () { return (<>); } -export function MetadataEditorForm ({ metadata, definition, schema, current, onChange }) { +export function MetadataEditorForm({ metadata, definition, schema, current, onChange, validator }) { const [locked, setLocked] = React.useState(true); @@ -61,7 +61,8 @@ export function MetadataEditorForm ({ metadata, definition, schema, current, onC widgets={widgets} liveValidate={true} transformErrors={transformErrors} - ErrorList={ErrorListTemplate}> + ErrorList={ErrorListTemplate} + validate={validator}> <> diff --git a/ui/src/app/metadata/hoc/MetadataFormContext.js b/ui/src/app/metadata/hoc/MetadataFormContext.js index f03035a03..9637b8fa7 100644 --- a/ui/src/app/metadata/hoc/MetadataFormContext.js +++ b/ui/src/app/metadata/hoc/MetadataFormContext.js @@ -56,9 +56,12 @@ function reducer(state, action) { } /*eslint-disable react-hooks/exhaustive-deps*/ -function MetadataForm({ children }) { +function MetadataForm({ children, initial = {} }) { - const metadata = useFormattedMetadata(); + const metadata = { + ...useFormattedMetadata(), + ...initial + }; const [state, dispatch] = React.useReducer(reducer, { ...initialState, @@ -79,9 +82,16 @@ function useFormErrors () { return errors; } +function useExtraErrors (metadata, validator) { + return validator(metadata); +} + function usePagesWithErrors(definition) { const errorList = useFormErrors(); const erroredProperties = uniq(errorList.map((e) => { + if (!e.hasOwnProperty('property')) { + return 'common'; + } let name = e.property.split('.').filter((p) => !!p && p !== "")[0]; if (name.indexOf('[')) { name = name.split('[')[0]; @@ -100,7 +110,7 @@ function usePagesWithErrors(definition) { return pages; } -function useFormattedMetadata() { +function useFormattedMetadata(initial = {}) { const definition = React.useContext(MetadataDefinitionContext); const schema = React.useContext(MetadataSchemaContext); return definition.formatter(React.useContext(MetadataObjectContext), schema); @@ -139,6 +149,7 @@ export { useMetadataFormState, useMetadataFormData, useMetadataFormErrors, + useExtraErrors, MetadataForm, MetadataFormContext, Provider as MetadataFormProvider, diff --git a/ui/src/app/metadata/hooks/api.js b/ui/src/app/metadata/hooks/api.js index e72747c7e..7b957f67c 100644 --- a/ui/src/app/metadata/hooks/api.js +++ b/ui/src/app/metadata/hooks/api.js @@ -28,8 +28,8 @@ export function getSchemaPath(type) { return `/${schema[type]}`; } -export function useMetadataEntities(type = 'source', opts = {}) { - return useFetch(`${API_BASE_PATH}${getMetadataListPath(type)}`, opts); +export function useMetadataEntities(type = 'source', opts = {}, onMount) { + return useFetch(`${API_BASE_PATH}${getMetadataListPath(type)}`, opts, onMount); } export function useMetadataEntity(type = 'source', opts = { @@ -63,4 +63,12 @@ export function useMetadataHistory(type, id, opts = {}, i) { export function useMetadataSources(opts = {}, onMount) { return useFetch(`${API_BASE_PATH}${getMetadataListPath('source')}`, opts, onMount); +} + +export function useMetadataProviders(opts = {}, onMount) { + return useFetch(`${API_BASE_PATH}${getMetadataListPath('provider')}`, opts, onMount); +} + +export function useMetadataProviderTypes(opts = {}, onMount = null) { + return useFetch(`${API_BASE_PATH}/ui/MetadataResolver/types`, opts, onMount); } \ No newline at end of file diff --git a/ui/src/app/metadata/hooks/configuration.js b/ui/src/app/metadata/hooks/configuration.js index da0ba0c28..dc23b874f 100644 --- a/ui/src/app/metadata/hooks/configuration.js +++ b/ui/src/app/metadata/hooks/configuration.js @@ -18,7 +18,5 @@ export function useMetadataConfiguration(models, schema, definition, limited = f return {}; } - const processed = definition.schemaPreprocessor ? definition.schemaPreprocessor(schema) : schema; - - return getLimitedConfigurationsFn(getConfigurationSections(models, definition, processed), limited); + return getLimitedConfigurationsFn(getConfigurationSections(models, definition, schema), limited); } diff --git a/ui/src/app/metadata/view/MetadataComparison.js b/ui/src/app/metadata/view/MetadataComparison.js index f7d5e06d5..566787a55 100644 --- a/ui/src/app/metadata/view/MetadataComparison.js +++ b/ui/src/app/metadata/view/MetadataComparison.js @@ -16,6 +16,7 @@ import Form from 'react-bootstrap/Form'; import { useTranslation } from '../../i18n/hooks'; import { MetadataFilterVersionList } from '../domain/filter/component/MetadataFilterVersionList'; import { MetadataFilterVersionContext } from '../domain/filter/component/MetadataFilterVersionContext'; +import { useMetadataSchema } from '../hooks/schema'; export function MetadataComparison () { @@ -25,6 +26,8 @@ export function MetadataComparison () { const schema = React.useContext(MetadataSchemaContext); const definition = React.useContext(MetadataDefinitionContext); + const processed = useMetadataSchema(definition, schema); + const [limited, setLimited] = React.useState(false); const toggleLimited = useTranslation('action.view-only-changes'); @@ -39,7 +42,7 @@ export function MetadataComparison () { {versions && {(v) => - + {(config) =>
2 ? 'container-fluid' : 'container'}>
{isLast &&
- + {(config) => }
diff --git a/ui/src/app/metadata/wizard/MetadataWizardForm.js b/ui/src/app/metadata/wizard/MetadataWizardForm.js index 8597eb577..1a815e54e 100644 --- a/ui/src/app/metadata/wizard/MetadataWizardForm.js +++ b/ui/src/app/metadata/wizard/MetadataWizardForm.js @@ -12,7 +12,7 @@ function ErrorListTemplate () { return (<>); } -export function MetadataWizardForm ({ metadata, definition, schema, current, onChange, onBlur = false }) { +export function MetadataWizardForm ({ metadata, definition, schema, current, onChange, onBlur = false, validator }) { const {uiSchema} = useUiSchema(definition, schema, current); @@ -42,7 +42,8 @@ export function MetadataWizardForm ({ metadata, definition, schema, current, onC widgets={widgets} liveValidate={true} transformErrors={transformErrors} - ErrorList={ErrorListTemplate}> + ErrorList={ErrorListTemplate} + validate={validator}> <>
diff --git a/ui/src/app/metadata/wizard/Wizard.js b/ui/src/app/metadata/wizard/Wizard.js index 44efbfbb3..cc52249fe 100644 --- a/ui/src/app/metadata/wizard/Wizard.js +++ b/ui/src/app/metadata/wizard/Wizard.js @@ -84,6 +84,11 @@ function useNextPage() { return definition.steps[idx + 1]; } +function useFirstPage() { + const definition = useMetadataDefinitionContext(); + return definition.steps[0]; +} + function useLastPage () { const definition = useMetadataDefinitionContext(); return definition.steps[definition.steps.length - 1]; @@ -114,6 +119,7 @@ export { useCurrentPage, useNextPage, usePreviousPage, + useFirstPage, useLastPage, useIsFirstPage, useIsLastPage, diff --git a/ui/src/app/metadata/wizard/WizardNav.js b/ui/src/app/metadata/wizard/WizardNav.js index 9be7ed9fb..5fdc91def 100644 --- a/ui/src/app/metadata/wizard/WizardNav.js +++ b/ui/src/app/metadata/wizard/WizardNav.js @@ -7,6 +7,7 @@ import {Translate} from '../../i18n/components/translate'; import { useCurrentPage, useLastPage, + useFirstPage, useNextPage, usePreviousPage, setWizardIndexAction, @@ -18,12 +19,13 @@ export const ICONS = { INDEX: 'INDEX' } -export function WizardNav ({ disabled = false, onSave, saving }) { +export function WizardNav ({ disabled = false, onSave, saving, onRestart }) { const dispatch = useWizardDispatcher(); const current = useCurrentPage(); const previous = usePreviousPage(); + const first = useFirstPage(); const next = useNextPage(); const last = useLastPage(); @@ -31,13 +33,21 @@ export function WizardNav ({ disabled = false, onSave, saving }) { dispatch(setWizardIndexAction(idx)); }; + const onPrevious = (idx) => { + if (idx === first.id && onRestart) { + onRestart(); + } else { + onSetIndex(idx); + } + }; + const currentIcon = (last && current.index === last.index) ? : current.index; return (