From 8ff8d41461b8864cb103fa94c7b4d5a679bdeb6b Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 10 May 2021 15:16:34 -0700 Subject: [PATCH] Fixed issues with validation from page to page --- .../schema/provider/dynamic-http.schema.json | 1 - .../provider/filebacked-http.schema.json | 9 +- .../templates/ObjectFieldTemplate.js | 4 +- .../form/component/widgets/CheckboxWidget.js | 2 +- .../form/component/widgets/OptionWidget.js | 4 +- .../app/form/component/widgets/TextWidget.js | 1 - ui/src/app/metadata/Metadata.js | 6 +- .../component/properties/ArrayProperty.js | 10 +- .../domain/provider/BaseProviderDefinition.js | 2 +- .../DynamicHttpMetadataProviderDefinition.js | 2 +- .../FileSystemMetadataProviderDefinition.js | 2 +- .../LocalDynamicMetadataProviderDefinition.js | 2 +- .../domain/source/SourceDefinition.js | 2 +- ui/src/app/metadata/editor/MetadataEditor.js | 49 +++++--- .../app/metadata/editor/MetadataEditorForm.js | 18 +-- .../app/metadata/editor/MetadataEditorNav.js | 30 +++-- .../app/metadata/hoc/MetadataFormContext.js | 116 ++++++++++++++++++ ui/src/app/metadata/view/MetadataEdit.js | 11 ++ 18 files changed, 198 insertions(+), 73 deletions(-) create mode 100644 ui/src/app/metadata/hoc/MetadataFormContext.js create mode 100644 ui/src/app/metadata/view/MetadataEdit.js diff --git a/ui/public/assets/schema/provider/dynamic-http.schema.json b/ui/public/assets/schema/provider/dynamic-http.schema.json index 24e77122d..e752ad491 100644 --- a/ui/public/assets/schema/provider/dynamic-http.schema.json +++ b/ui/public/assets/schema/provider/dynamic-http.schema.json @@ -391,7 +391,6 @@ "title": "", "description": "", "type": "array", - "additionalItems": true, "items": [ { "$id": "RequiredValidUntil", diff --git a/ui/public/assets/schema/provider/filebacked-http.schema.json b/ui/public/assets/schema/provider/filebacked-http.schema.json index 7c3984a68..5c921eec6 100644 --- a/ui/public/assets/schema/provider/filebacked-http.schema.json +++ b/ui/public/assets/schema/provider/filebacked-http.schema.json @@ -333,7 +333,6 @@ "title": "", "description": "", "type": "array", - "additionalItems": true, "items": [ { "$id": "RequiredValidUntil", @@ -376,9 +375,7 @@ { "properties": { "requireSignedRoot": { - "enum": [ - true - ] + "const": true } }, "required": [ @@ -388,9 +385,7 @@ { "properties": { "requireSignedRoot": { - "enum": [ - false - ] + "const": false } } } diff --git a/ui/src/app/form/component/templates/ObjectFieldTemplate.js b/ui/src/app/form/component/templates/ObjectFieldTemplate.js index a0106f4a1..a6b21af7c 100644 --- a/ui/src/app/form/component/templates/ObjectFieldTemplate.js +++ b/ui/src/app/form/component/templates/ObjectFieldTemplate.js @@ -21,8 +21,6 @@ const ObjectFieldTemplate = ({ const displayTitle = (uiSchema["ui:title"] || (title && schema.title)); - console.log(properties); - return ( <> {!hidden && @@ -52,7 +50,7 @@ const ObjectFieldTemplate = ({ {group.title && } {properties.filter(p => group.fields.indexOf(p.name) > -1).map((element, eIdx) => ( - {element.content}{console.log(element.content)} + {element.content} ))} } diff --git a/ui/src/app/form/component/widgets/CheckboxWidget.js b/ui/src/app/form/component/widgets/CheckboxWidget.js index 4e02c38c1..b1833159c 100644 --- a/ui/src/app/form/component/widgets/CheckboxWidget.js +++ b/ui/src/app/form/component/widgets/CheckboxWidget.js @@ -29,7 +29,7 @@ const CheckboxWidget = (props) => { target: { checked }, }) => onFocus(id, checked); - const desc = label || schema.description; + // const desc = label || schema.description; return ( ( @@ -44,7 +44,7 @@ const OptionWidget = ({ const _onChange = (selected) => onChange(selected[0] === '' ? options.emptyValue : selected[0]); const _onBlur = ({ target: { value } }) => onBlur(id, value); const _onFocus = ({ target: { value } }) => onFocus(id, value); - const inputType = (type || schema.type) === 'string' ? 'text' : `${type || schema.type}`; + // const inputType = (type || schema.type) === 'string' ? 'text' : `${type || schema.type}`; const opts = Array.isArray(options) || options.enumOptions ? options : schema.examples ? schema.examples : uiSchema.options ? uiSchema.options : []; diff --git a/ui/src/app/form/component/widgets/TextWidget.js b/ui/src/app/form/component/widgets/TextWidget.js index 2688a9116..583e24291 100644 --- a/ui/src/app/form/component/widgets/TextWidget.js +++ b/ui/src/app/form/component/widgets/TextWidget.js @@ -28,7 +28,6 @@ const TextWidget = ({ const _onBlur = ({ target: { value } }) => onBlur(id, value); const _onFocus = ({target: { value }} ) => onFocus(id, value); const inputType = (type || schema.type) === 'string' ? 'text' : `${type || schema.type}`; - console.log(props) // const classNames = [rawErrors.length > 0 ? "is-invalid" : "", type === 'file' ? 'custom-file-label': ""] return ( diff --git a/ui/src/app/metadata/Metadata.js b/ui/src/app/metadata/Metadata.js index 315846319..ecb53a3ec 100644 --- a/ui/src/app/metadata/Metadata.js +++ b/ui/src/app/metadata/Metadata.js @@ -3,13 +3,13 @@ import { Switch, Route, useRouteMatch } from 'react-router-dom'; import { MetadataOptions } from './view/MetadataOptions'; import { MetadataDetail } from './component/MetadataDetail'; import { MetadataHistory } from './view/MetadataHistory'; -import { MetadataEditor } from './editor/MetadataEditor'; import { MetadataSelector } from './hoc/MetadataSelector'; import { MetadataSchema } from './hoc/MetadataSchema'; import { MetadataXmlLoader } from './hoc/MetadataXmlLoader'; import { MetadataXml } from './view/MetadataXml'; import { MetadataComparison } from './view/MetadataComparison'; import { MetadataVersion } from './view/MetadataVersion'; +import { MetadataEdit } from './view/MetadataEdit'; export function Metadata () { @@ -46,7 +46,9 @@ export function Metadata () { } /> - + + + } /> diff --git a/ui/src/app/metadata/component/properties/ArrayProperty.js b/ui/src/app/metadata/component/properties/ArrayProperty.js index 446abfdd9..4b4b03919 100644 --- a/ui/src/app/metadata/component/properties/ArrayProperty.js +++ b/ui/src/app/metadata/component/properties/ArrayProperty.js @@ -1,6 +1,6 @@ import { faEye } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React, { useState } from 'react'; +import React from 'react'; import Translate from '../../../i18n/components/translate'; import { usePropertyWidth } from './hooks'; import { PropertyValue } from './PropertyValue'; @@ -30,10 +30,10 @@ export function ArrayProperty ({ property, columns, onPreview }) { React.useEffect(() => { setKeys(property.value.reduce((val, version) => version ? version.length > val ? version.length : val : val, 0)); - setDataList(property.items?.enum); + console.log(property); - console.log(property, keys); + setDataList(property.items?.enum); }, [property]); React.useEffect(() => { @@ -76,7 +76,7 @@ export function ArrayProperty ({ property, columns, onPreview }) { } {property?.items?.type === 'string' && <> - { property?.items?.enum?.length ? + { property?.items?.enum?.length && property.uniqueItems ? <> {dataList.map((item, itemIdx) =>
@@ -97,7 +97,7 @@ export function ArrayProperty ({ property, columns, onPreview }) { { property.name } {property.value.map((v, vidx) => - {(!v || !v.length) &&

-

} + {(!v || !v.length) &&

-

} {(v && v.length > 0) &&
    {v.map((item, idx) => diff --git a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js index eac73a10e..6861f2ae3 100644 --- a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js @@ -7,7 +7,7 @@ export const BaseProviderDefinition = { ...changes, metadataFilters: [ ...changes.metadataFilters.filter((filter, filterName) => ( - Object.keys(filter).length > 1 + Object.keys(filter).length > 0 )) ] }) : changes), diff --git a/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js index dfc5365e6..34505852c 100644 --- a/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js @@ -1,7 +1,7 @@ // import { metadataFilterProcessor } from './utility/providerFilterProcessor'; import { BaseProviderDefinition, HttpMetadataResolverAttributesSchema, MetadataFilterPluginsSchema } from './BaseProviderDefinition'; -import API_BASE_PATH from '../../../App.constant'; +// import API_BASE_PATH from '../../../App.constant'; import defaultsDeep from 'lodash/defaultsDeep'; import { DurationOptions } from '../data'; diff --git a/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js index 229b45bac..150bcc074 100644 --- a/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js @@ -1,5 +1,5 @@ import defaultsDeep from 'lodash/defaultsDeep'; -import API_BASE_PATH from "../../../App.constant"; +// import API_BASE_PATH from "../../../App.constant"; import { BaseProviderDefinition } from "./BaseProviderDefinition"; import { DurationOptions } from '../data'; diff --git a/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js index 50e15b7ee..4afa15807 100644 --- a/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js @@ -1,6 +1,6 @@ import defaultsDeep from 'lodash/defaultsDeep'; -import API_BASE_PATH from "../../../App.constant"; +// import API_BASE_PATH from "../../../App.constant"; import {DurationOptions} from '../data'; import { BaseProviderDefinition } from "./BaseProviderDefinition"; diff --git a/ui/src/app/metadata/domain/source/SourceDefinition.js b/ui/src/app/metadata/domain/source/SourceDefinition.js index 53cb42cd6..04c2d8f4c 100644 --- a/ui/src/app/metadata/domain/source/SourceDefinition.js +++ b/ui/src/app/metadata/domain/source/SourceDefinition.js @@ -1,5 +1,5 @@ import defaultsDeep from 'lodash/defaultsDeep'; -import API_BASE_PATH from '../../../App.constant'; +// import API_BASE_PATH from '../../../App.constant'; export const SourceBase = { label: 'Metadata Source', diff --git a/ui/src/app/metadata/editor/MetadataEditor.js b/ui/src/app/metadata/editor/MetadataEditor.js index 56c852208..9400e2d9d 100644 --- a/ui/src/app/metadata/editor/MetadataEditor.js +++ b/ui/src/app/metadata/editor/MetadataEditor.js @@ -1,35 +1,32 @@ import { faCogs, faExclamationTriangle, faSave, faSpinner } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React from 'react'; -import { useParams } from 'react-router'; +import { useHistory, useParams } from 'react-router'; import Translate from '../../i18n/components/translate'; +import { MetadataFormContext, setFormDataAction, setFormErrorAction } from '../hoc/MetadataFormContext'; import { MetadataDefinitionContext, MetadataSchemaContext } from '../hoc/MetadataSchema'; -import { MetadataObjectContext } from '../hoc/MetadataSelector'; import { MetadataEditorForm } from './MetadataEditorForm'; import { MetadataEditorNav } from './MetadataEditorNav'; export function MetadataEditor () { const { type, id, section } = useParams(); - - const base = React.useContext(MetadataObjectContext); + const history = useHistory(); const definition = React.useContext(MetadataDefinitionContext); const schema = React.useContext(MetadataSchemaContext); - const [invalid, setInvalid] = React.useState(false); - const [saving, setSaving] = React.useState(false); + const [invalid] = React.useState(false); + const [saving] = React.useState(false); - const [updates, setUpdates] = React.useState(base); + const { state, dispatch } = React.useContext(MetadataFormContext); + const { metadata, errors } = state; - const onChange = (updates) => { - setUpdates(definition.parser(updates)); + const onChange = (changes) => { + dispatch(setFormDataAction(changes.formData)); + dispatch(setFormErrorAction(section, changes.errors)); }; - React.useEffect(() => { - setUpdates(base); - }, [base]); - const save = () => { console.log('save!'); }; @@ -38,15 +35,19 @@ export function MetadataEditor () { console.log('cancel!'); }; + const onNavigate = (path) => { + history.push(path) + }; + return (
    -
    +
      - Edit metadata {type} - {base.serviceProviderName || base.name} + Edit metadata {type} - {metadata.serviceProviderName || metadata.name}
    @@ -55,10 +56,12 @@ export function MetadataEditor () {
    + format='dropdown' + errors={errors}>
    @@ -88,15 +91,23 @@ export function MetadataEditor () {
    - + format='tabs' + errors={errors}>
    - + {definition && schema && metadata && + + }
    diff --git a/ui/src/app/metadata/editor/MetadataEditorForm.js b/ui/src/app/metadata/editor/MetadataEditorForm.js index 34cf03c68..82bca797b 100644 --- a/ui/src/app/metadata/editor/MetadataEditorForm.js +++ b/ui/src/app/metadata/editor/MetadataEditorForm.js @@ -16,17 +16,12 @@ export function MetadataEditorForm ({ metadata, definition, schema, current, onC const onSubmit = () => {}; - const [context, setContext] = React.useState(definition.steps.find(s => s.id === current)); - - React.useEffect(() => { - setContext(definition.steps.find(s => s.id === current)) - }, [current, definition]); return ( <>
    setData(formData) } + onChange={(form) => onChange(form) } onSubmit={() => onSubmit()} schema={schema} uiSchema={uiSchema} @@ -35,18 +30,9 @@ export function MetadataEditorForm ({ metadata, definition, schema, current, onC ArrayFieldTemplate={templates.ArrayFieldTemplate} fields={ fields } widgets={widgets} - liveValidate={true} - formContext={context}> + liveValidate={true}> <>
    -
    -
    -
    {JSON.stringify(data, null, 4)}
    -
    -
    -
    {JSON.stringify(schema, null, 4)}
    -
    -
    ); } \ No newline at end of file diff --git a/ui/src/app/metadata/editor/MetadataEditorNav.js b/ui/src/app/metadata/editor/MetadataEditorNav.js index 6304c5be8..ecbc587b1 100644 --- a/ui/src/app/metadata/editor/MetadataEditorNav.js +++ b/ui/src/app/metadata/editor/MetadataEditorNav.js @@ -1,16 +1,20 @@ -import { faBars } from '@fortawesome/free-solid-svg-icons'; +import { faBars, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React from 'react'; -import { NavLink } from 'react-router-dom'; import Dropdown from 'react-bootstrap/Dropdown'; import Translate from '../../i18n/components/translate'; +import { usePagesWithErrors } from '../hoc/MetadataFormContext'; -export function MetadataEditorNav ({ definition, current, base, children, format = 'tabs' }) { +export function MetadataEditorNav ({ definition, current, base, children, format = 'tabs', onNavigate }) { const [routes, setRoutes] = React.useState([]); const [active, setActive] = React.useState(null); + const errors = usePagesWithErrors(); + + console.log(errors); + React.useEffect(() => { setRoutes(definition ? definition.steps.map(step => ({ path: step.id, label: step.label })) : []) }, [definition]); @@ -29,13 +33,14 @@ export function MetadataEditorNav ({ definition, current, base, children, format {routes.map((route, idx) => - onNavigate(route.path)} aria-label={route.label}> - + )} {children && @@ -49,14 +54,17 @@ export function MetadataEditorNav ({ definition, current, base, children, format
    diff --git a/ui/src/app/metadata/hoc/MetadataFormContext.js b/ui/src/app/metadata/hoc/MetadataFormContext.js new file mode 100644 index 000000000..36db6a4ad --- /dev/null +++ b/ui/src/app/metadata/hoc/MetadataFormContext.js @@ -0,0 +1,116 @@ +import React from 'react'; +import { MetadataDefinitionContext } from './MetadataSchema'; +import { MetadataObjectContext } from './MetadataSelector'; + +const initialState = { + metadata: {}, + errors: {} +}; + +const MetadataFormContext = React.createContext(); + +const { Provider, Consumer } = MetadataFormContext; + +export const MetadataFormActions = { + SET_FORM_ERROR: 'set form error', + SET_FORM_DATA: 'set form data' +}; + +export const updateFormDataAction = (payload) => { + return { + type: MetadataFormActions.UPDATE_FORM_DATA, + payload + } +} + +export const setFormDataAction = (payload) => { + return { + type: MetadataFormActions.SET_FORM_DATA, + payload + } +} + +export const setFormErrorAction = (page, errors) => { + return { + type: MetadataFormActions.SET_FORM_ERROR, + payload: { + page, + errors + } + } +} + +function reducer(state, action) { + switch (action.type) { + case MetadataFormActions.SET_FORM_ERROR: + return { + ...state, + errors: { + ...state.errors, + [action.payload.page]: action.payload.errors + } + }; + case MetadataFormActions.SET_FORM_DATA: + return { + ...state, + metadata: action.payload + }; + case MetadataFormActions.UPDATE_FORM_DATA: + return { + ...state, + metadata: action.payload.metadata, + errors: { + ...action.payload.errors + } + }; + default: + return state; + } +} + +/*eslint-disable react-hooks/exhaustive-deps*/ +function MetadataForm({ children }) { + + const metadata = useFormattedMetadata(); + + const [state, dispatch] = React.useReducer(reducer, { + ...initialState, + metadata + }); + + const contextValue = React.useMemo(() => ({ state, dispatch }), [state, dispatch]); + + return ( + {children} + ); +} + +function useFormErrors () { + const { state } = React.useContext(MetadataFormContext); + const { errors } = state; + + console.log(errors) + + return errors; +} + +function usePagesWithErrors() { + const errors = useFormErrors(); + + return Object.keys(errors).filter(p => errors[p] && Array.isArray(errors[p]) && errors[p].length > 0); +} + +function useFormattedMetadata() { + const definition = React.useContext(MetadataDefinitionContext); + return definition.formatter(React.useContext(MetadataObjectContext)) +} + +export { + usePagesWithErrors, + useFormErrors, + useFormattedMetadata, + MetadataForm, + MetadataFormContext, + Provider as MetadataFormProvider, + Consumer as MetadataFormConsumer +}; \ No newline at end of file diff --git a/ui/src/app/metadata/view/MetadataEdit.js b/ui/src/app/metadata/view/MetadataEdit.js new file mode 100644 index 000000000..65fce53bf --- /dev/null +++ b/ui/src/app/metadata/view/MetadataEdit.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { MetadataForm } from '../hoc/MetadataFormContext'; +import { MetadataEditor } from '../editor/MetadataEditor'; + +export function MetadataEdit() { + return ( + + + + ); +} \ No newline at end of file