diff --git a/ui/public/assets/schema/provider/dynamic-http.schema.json b/ui/public/assets/schema/provider/dynamic-http.schema.json index f4f96fe4b..a948e7be6 100644 --- a/ui/public/assets/schema/provider/dynamic-http.schema.json +++ b/ui/public/assets/schema/provider/dynamic-http.schema.json @@ -116,7 +116,7 @@ { "properties": { "initializeFromPersistentCacheInBackground": { - "const": true + "enum": [true] }, "backgroundInitializationFromCacheDelay": { "title": "label.background-init-from-cache-delay", @@ -129,9 +129,10 @@ { "properties": { "initializeFromPersistentCacheInBackground": { - "const": false + "enum": [false] } } + } ] } diff --git a/ui/src/app/App.js b/ui/src/app/App.js index 9d37b1f0a..ee40bb538 100644 --- a/ui/src/app/App.js +++ b/ui/src/app/App.js @@ -23,6 +23,7 @@ import { UserConfirmation, ConfirmWindow } from './core/components/UserConfirmat import { NewSource } from './metadata/new/NewSource'; import { NewProvider } from './metadata/new/NewProvider'; import { Filter } from './metadata/Filter'; +import { Contention } from './metadata/contention/ContentionContext'; function App() { @@ -43,30 +44,32 @@ function App() { - - {(message, confirm, confirmCallback, setConfirm, getConfirmation) => - - - - - - - - - - - - - - - - - - - - - } - + + + {(message, confirm, confirmCallback, setConfirm, getConfirmation) => + + + + + + + + + + + + + + + + + + + + + } + + diff --git a/ui/src/app/form/component/widgets/AttributeReleaseWidget.js b/ui/src/app/form/component/widgets/AttributeReleaseWidget.js index c547891d8..3c68c4107 100644 --- a/ui/src/app/form/component/widgets/AttributeReleaseWidget.js +++ b/ui/src/app/form/component/widgets/AttributeReleaseWidget.js @@ -39,7 +39,6 @@ const AttributeReleaseWidget = ({ const all = (enumOptions).map(({ value }) => value); if (checked) { - console.log(selectValue(option.value, value, all)) onChange(selectValue(option.value, value, all)); } else { onChange(deselectValue(option.value, value)); diff --git a/ui/src/app/form/component/widgets/OptionWidget.js b/ui/src/app/form/component/widgets/OptionWidget.js index d5e2c4fd0..65af5f057 100644 --- a/ui/src/app/form/component/widgets/OptionWidget.js +++ b/ui/src/app/form/component/widgets/OptionWidget.js @@ -44,6 +44,9 @@ const OptionWidget = ({ uiSchema, ...props }) => { + + console.log(value, props) + const _onChange = (selected) => onChange(selected[0] === '' ? options.emptyValue : selected[0]); const _onBlur = ({ target: { value } }) => onBlur(id, value); const _onFocus = ({ target: { value } }) => onFocus(id, value); diff --git a/ui/src/app/metadata/Filter.js b/ui/src/app/metadata/Filter.js index d7e43f8c6..fce73f351 100644 --- a/ui/src/app/metadata/Filter.js +++ b/ui/src/app/metadata/Filter.js @@ -1,6 +1,7 @@ import React from 'react'; import { Switch, Route, useRouteMatch, Redirect } from 'react-router-dom'; import { MetadataFilterList } from './editor/MetadataFilterList'; +import MetadataFilterSelector from './hoc/MetadataFilterSelector'; import MetadataSchema from './hoc/MetadataSchema'; import MetadataSelector from './hoc/MetadataSelector'; import { NewFilter } from './new/NewFilter'; @@ -25,7 +26,9 @@ export function Filter() { } /> - + + + } /> diff --git a/ui/src/app/metadata/component/properties/ObjectProperty.js b/ui/src/app/metadata/component/properties/ObjectProperty.js index a04e082d0..d42107161 100644 --- a/ui/src/app/metadata/component/properties/ObjectProperty.js +++ b/ui/src/app/metadata/component/properties/ObjectProperty.js @@ -11,9 +11,6 @@ export function ObjectProperty ({ property, columns, onPreview }) { case 'array': return case 'object': - if (prop.widget && prop.widget.id && prop.widget.id === 'filter-target') { - console.log(prop); - } return {prop.widget && prop.widget.id && prop.widget.id === 'filter-target' ? diff --git a/ui/src/app/metadata/contention/ContentionContext.js b/ui/src/app/metadata/contention/ContentionContext.js new file mode 100644 index 000000000..1224ce929 --- /dev/null +++ b/ui/src/app/metadata/contention/ContentionContext.js @@ -0,0 +1,157 @@ +import React from "react"; + +import { updatedDiff } from 'deep-object-diff'; +import { removeNull } from '../../core/utility/remove_null'; + +import {ContentionModal} from './component/ContentionModal'; + +const ContentionContext = React.createContext(); + +const { Provider, Consumer } = ContentionContext; + +const initialState = { + theirs: null, + ours: null, + base: null, + ourChanges: null, + theirChanges: null, + resolution: null, + reject: null, + resolve: null, + show: false +}; + +export const ContentionActions = { + OPEN_CONTENTION_MODAL: 'open contention', + END_CONTENTION: 'end contention' +}; + +const keys = [ + 'version', + 'modifiedDate', + 'createdDate', + 'createdBy', + 'modifiedBy', + 'audId', + 'resourceId', + 'current', + '@type' +]; + +const filterKeys = (key => (keys.indexOf(key) === -1)); + + +const getContention = (base, ours, theirs) => { + + let theirDiff = updatedDiff(base, theirs); + let ourDiff = updatedDiff(base, removeNull(ours)); + let ourKeys = Object.keys(ourDiff).filter(filterKeys); + let theirKeys = Object.keys(theirDiff).filter(filterKeys); + + const ourChanges = ourKeys.map(key => getChangeItem(key, ours)); + const theirChanges = theirKeys.map(key => getChangeItem(key, theirs, ourKeys)); + const resolution = { ...base, ...ours, version: theirs.version } + + return { + ourChanges, + theirChanges, + resolution + }; +} + +const getChangeItem = (key, collection, compare = []) => { + return { + label: key, + value: collection[key], + conflict: compare.some(o => o === key) + }; +} + + +export const openContentionModalAction = (base, theirs, ours, resolve, reject) => { + + const { ourChanges, theirChanges, resolution } = getContention(base, ours, theirs); + + return { + type: ContentionActions.OPEN_CONTENTION_MODAL, + payload: { + base, + theirs, + ours, + theirChanges, + ourChanges, + resolution, + reject, + resolve + } + } +} + +export const resolveContentionAction = () => { + return { + type: ContentionActions.END_CONTENTION, + } +} + +function reducer(state, action) { + switch (action.type) { + case ContentionActions.OPEN_CONTENTION_MODAL: + return { + ...action.payload, + show: true + }; + case ContentionActions.END_CONTENTION: + return { + ...initialState, + show: false + }; + default: + throw new Error(); + } +} + +/*eslint-disable react-hooks/exhaustive-deps*/ +function Contention({ children }) { + const [state, dispatch] = React.useReducer(reducer, initialState); + + const { show, theirs, theirChanges, ourChanges, resolution, reject, resolve } = state; + + const onReject = () => { + reject(theirs); + dispatch(resolveContentionAction()); + } + + const onResolve = () => { + resolve(resolution); + dispatch(resolveContentionAction()); + } + + const contextValue = React.useMemo(() => ({ state, dispatch }), [state, dispatch]); + + return ( + + onReject()} + onUseOurs={() => onResolve(resolution)} + backdrop="static" + keyboard={false} /> + {children} + + ); +} + +function useContentionDispatcher() { + const { dispatch } = React.useContext(ContentionContext); + return dispatch; +} + +export { + Contention, + ContentionContext, + useContentionDispatcher, + Provider as ContentionProvider, + Consumer as ContentionConsumer +}; \ No newline at end of file diff --git a/ui/src/app/metadata/contention/component/ChangeItem.js b/ui/src/app/metadata/contention/component/ChangeItem.js new file mode 100644 index 000000000..7c107beb4 --- /dev/null +++ b/ui/src/app/metadata/contention/component/ChangeItem.js @@ -0,0 +1,131 @@ +import React from 'react'; +import Translate from '../../../i18n/components/translate'; + +export const ValueTypes = { + array: 'array', + object: 'object', + string: 'string', + number: 'number', + symbol: 'symbol', + boolean: 'boolean', + function: 'function', + undefined: 'undefined' +} + + +function getType(value) { + return Array.isArray(value) ? 'array' : typeof value; +} + +function getValue(val, type) { + switch (type) { + case 'object': { + return Object.keys(val).map(k => ({ label: k, value: val[k] })); + } + case 'array': { + return parseArray(val); + } + default: { + return val; + } + } +} + +function parseArray(list) { + switch (getType(list[0])) { + case 'string': { + return { + type: 'string', + values: list + }; + } + default: { + return { + type: 'object', + headings: list.reduce((arr, o) => { + return Object.keys(o).reduce((a, k) => { + if (a.indexOf(k) === -1) { a.push(k); } + return a; + }, arr); + }, []), + values: list + }; + } + } +} + +export function ChangeItem ({item}) { + + const isList = Array.isArray(item.value); + + const type = getType(item.value); + const display = getValue(item.value, type); + + return ( + + + {item.label} + {item.conflict && + + + Conflict + + } + + { type === ValueTypes.array ? + + {display.type === ValueTypes.object && + + + + {display.headings.map((heading, idx) => + + {heading} + + )} + + + + {display.values.map((obj, oidx) => ( + + {display.headings.map((key, kidx) => + { obj[key] } + )} + + ))} + + + } + { display.type === ValueTypes.string && + + {display.values.map((val, vidx) => + + {val} + + )} + + + } + + + + + : + type === ValueTypes.object ? + + {display.map((item, iidx) => + + {item.label} + { item.value } + + )} + + : + + {!isList && } + + } + + + ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/contention/component/ContentionModal.js b/ui/src/app/metadata/contention/component/ContentionModal.js new file mode 100644 index 000000000..1bd6e0889 --- /dev/null +++ b/ui/src/app/metadata/contention/component/ContentionModal.js @@ -0,0 +1,107 @@ +import React from 'react'; + +import Modal from 'react-bootstrap/Modal'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; + +import Translate from '../../../i18n/components/translate'; +import { useTranslator } from '../../../i18n/hooks'; +import { ChangeItem } from './ChangeItem'; +import Alert from 'react-bootstrap/esm/Alert'; + +export function ContentionModal ({ theirs = [], ours = [], onUseTheirs, onUseOurs, ...props }) { + + const translator = useTranslator(); + + const resolutionObj = {}; + const rejectionObj = {}; + + return ( + + + Data Version Contention + + + {/* + + {JSON.stringify(theirs, null, 4)} + + + {JSON.stringify(ours, null, 4)} + + */} + + + + {theirs && theirs.length > 0 ? + + A newer version of this metadata source has been saved. Below are a list of changes. You can use your changes or their changes. + + : + + There was a problem saving due to a mismatched version. + + } + + {theirs && theirs.length > 0 && + + + + + My Changes + + + {ours && ours.map((item, idx) => + + + + )} + {ours && ours.length === 0 && + + You haven't made any changes. + + } + + + + + + + Their Changes + + + {theirs.map((item, idx) => + + + + )} + + + + + } + + {theirs && theirs.length < 1 ? + + onUseTheirs()}>Cancel + + : + ours && ours.length < 1 ? + + onUseTheirs()}>Get latest + + : + + onUseOurs()} disabled={!resolutionObj}> + Use My Changes + + onUseTheirs()} disabled={!rejectionObj}> + Use Their Changes + + + } + + ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/domain/filter/EntityAttributesFilterDefinition.js b/ui/src/app/metadata/domain/filter/EntityAttributesFilterDefinition.js index 3440d9b86..8956c9110 100644 --- a/ui/src/app/metadata/domain/filter/EntityAttributesFilterDefinition.js +++ b/ui/src/app/metadata/domain/filter/EntityAttributesFilterDefinition.js @@ -41,8 +41,6 @@ export const EntityAttributesFilterWizard = { const filters = current ? data.filter(s => s.resourceId !== current.resourceId) : data; const names = filters.map(s => s.name); - console.log(current) - return (formData, errors) => { if (names.indexOf(formData.name) > -1) { errors.name.addError('message.name-unique'); @@ -59,7 +57,6 @@ export const EntityAttributesFilterWizard = { } }, parser: (changes) => { - console.log(changes); return { ...changes, relyingPartyOverrides: removeNull(changes) diff --git a/ui/src/app/metadata/domain/filter/component/MetadataFilterEditorList.js b/ui/src/app/metadata/domain/filter/component/MetadataFilterEditorList.js index dfeb04c34..756ed1966 100644 --- a/ui/src/app/metadata/domain/filter/component/MetadataFilterEditorList.js +++ b/ui/src/app/metadata/domain/filter/component/MetadataFilterEditorList.js @@ -10,8 +10,6 @@ import { Translate } from '../../../../i18n/components/translate'; export function MetadataFilterEditorList ({provider, filters, onDelete, onUpdate, loading}) { - const disabled = loading; - return ( {(ordered, first, last, onOrderUp, onOrderDown) => @@ -57,7 +55,7 @@ export function MetadataFilterEditorList ({provider, filters, onDelete, onUpdate - + Edit diff --git a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js index da2c4dfe1..d83d23a42 100644 --- a/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/BaseProviderDefinition.js @@ -20,14 +20,16 @@ export const BaseProviderDefinition = { } }, parser: (changes) => { - return (changes.metadataFilters ? ({ + const parsed = (changes.metadataFilters ? ({ ...changes, metadataFilters: [ ...changes.metadataFilters.filter((filter, filterName) => ( Object.keys(filter).filter(k => k !== '@type').length > 0 )) ] - }) : changes) + }) : changes); + + return parsed; }, formatter: (changes, schema) => { const filterSchema = schema?.properties?.metadataFilters; diff --git a/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js index 73950a97b..ee1ef3ceb 100644 --- a/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/DynamicHttpMetadataProviderDefinition.js @@ -61,6 +61,12 @@ export const DynamicHttpMetadataProviderWizard = { '@type' ] }, + { + size: 8, + fields: [ + 'enabled' + ] + }, { size: 8, fields: [ @@ -87,12 +93,6 @@ export const DynamicHttpMetadataProviderWizard = { fields: [ 'httpMetadataResolverAttributes' ] - }, - { - size: 8, - fields: [ - 'enabled' - ] } ] }, @@ -151,7 +151,10 @@ export const DynamicHttpMetadataProviderWizard = { backgroundInitializationFromCacheDelay: { 'ui:widget': 'OptionWidget', options: DurationOptions, - 'ui:placeholder': 'label.duration' + 'ui:placeholder': 'label.duration', + visibleIf: { + initializeFromPersistentCacheInBackground: true + } } }, metadataFilters: MetadataFilterPluginsSchema, @@ -207,5 +210,8 @@ export const DynamicHttpMetadataProviderEditor = { } ], uiSchema: defaultsDeep({ + '@type': { + 'ui:readonly': true + } }, DynamicHttpMetadataProviderWizard.uiSchema) }; \ No newline at end of file diff --git a/ui/src/app/metadata/domain/provider/FileBackedHttpMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/FileBackedHttpMetadataProviderDefinition.js index fb4d3818d..155ac915f 100644 --- a/ui/src/app/metadata/domain/provider/FileBackedHttpMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/FileBackedHttpMetadataProviderDefinition.js @@ -68,6 +68,12 @@ export const FileBackedHttpMetadataProviderWizard = { '@type' ] }, + { + size: 8, + fields: [ + 'enabled' + ] + }, { size: 8, fields: [ @@ -99,12 +105,6 @@ export const FileBackedHttpMetadataProviderWizard = { fields: [ 'httpMetadataResolverAttributes' ] - }, - { - size: 8, - fields: [ - 'enabled' - ] } ] }, @@ -225,5 +225,8 @@ export const FileBackedHttpMetadataProviderEditor = { } ], uiSchema: defaultsDeep({ + '@type': { + 'ui:readonly': true + } }, FileBackedHttpMetadataProviderWizard.uiSchema) }; diff --git a/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js index db0816397..84228dced 100644 --- a/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/FileSystemMetadataProviderDefinition.js @@ -54,21 +54,21 @@ export const FileSystemMetadataProviderWizard = { { size: 8, fields: [ - 'xmlId', - 'metadataFile', - 'doInitialization', + 'enabled' ] }, { size: 8, fields: [ - 'reloadableMetadataResolverAttributes' + 'xmlId', + 'metadataFile', + 'doInitialization', ] }, { size: 8, fields: [ - 'enabled' + 'reloadableMetadataResolverAttributes' ] } ] @@ -168,5 +168,8 @@ export const FileSystemMetadataProviderEditor = { } ], uiSchema: defaultsDeep({ + '@type': { + 'ui:readonly': true + } }, FileSystemMetadataProviderWizard.uiSchema) }; diff --git a/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js b/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js index 9a49fe955..6e4308da4 100644 --- a/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js +++ b/ui/src/app/metadata/domain/provider/LocalDynamicMetadataProviderDefinition.js @@ -54,21 +54,21 @@ export const LocalDynamicMetadataProviderWizard = { { size: 8, fields: [ - 'xmlId', - 'sourceDirectory' + 'enabled' ] }, { size: 8, fields: [ - 'dynamicMetadataResolverAttributes' - ], + 'xmlId', + 'sourceDirectory' + ] }, { size: 8, fields: [ - 'enabled' - ] + 'dynamicMetadataResolverAttributes' + ], } ] }, @@ -112,6 +112,9 @@ export const LocalDynamicMetadataProviderWizard = { export const LocalDynamicMetadataProviderEditor = { ...LocalDynamicMetadataProviderWizard, uiSchema: defaultsDeep({ + '@type': { + 'ui:readonly': true + } }, LocalDynamicMetadataProviderWizard.uiSchema), steps: [ { diff --git a/ui/src/app/metadata/editor/MetadataEditor.js b/ui/src/app/metadata/editor/MetadataEditor.js index d8cbcb036..7ea1632cc 100644 --- a/ui/src/app/metadata/editor/MetadataEditor.js +++ b/ui/src/app/metadata/editor/MetadataEditor.js @@ -10,10 +10,11 @@ import { MetadataDefinitionContext, MetadataSchemaContext } from '../hoc/Metadat import { MetadataEditorForm } from './MetadataEditorForm'; import { MetadataEditorNav } from './MetadataEditorNav'; -import { useMetadataEntities, useMetadataEntity } from '../hooks/api'; -import { MetadataObjectContext } from '../hoc/MetadataSelector'; +import { getMetadataPath, useMetadataEntities, useMetadataUpdater } from '../hooks/api'; +import { useMetadataObject } from '../hoc/MetadataSelector'; import { NavLink } from 'react-router-dom'; import { useTranslator } from '../../i18n/hooks'; +import API_BASE_PATH from '../../App.constant'; export function MetadataEditor () { @@ -21,13 +22,15 @@ export function MetadataEditor () { const { type, id, section } = useParams(); - const { put, response, saving } = useMetadataEntity(type, {}, []); + const current = useMetadataObject(); + + const { update, loading } = useMetadataUpdater(`${ API_BASE_PATH }${getMetadataPath(type)}`, current); const { data } = useMetadataEntities(type, {}, []); const history = useHistory(); const definition = React.useContext(MetadataDefinitionContext); const schema = React.useContext(MetadataSchemaContext); - const current = React.useContext(MetadataObjectContext); + const { state, dispatch } = React.useContext(MetadataFormContext); const { metadata, errors } = state; @@ -38,11 +41,14 @@ export function MetadataEditor () { // setBlocking(true); }; - async function save(metadata) { - await put(`/${id}`, definition.parser(metadata)); - if (response.ok) { - gotoDetail({ refresh: true }); - } + function save(metadata) { + update(`/${id}`, definition.parser(metadata)) + .then(() => { + gotoDetail({ refresh: true }); + }) + .catch(err => { + window.location.reload(); + }); }; const cancel = () => { @@ -62,8 +68,6 @@ export function MetadataEditor () { const validator = definition.validator(data, current); - // console.log(errors); - return ( save(metadata)} - disabled={errors.length > 0 || saving} + disabled={errors.length > 0 || loading} aria-label="Save changes to the metadata source. You will return to the dashboard"> - + Save diff --git a/ui/src/app/metadata/editor/MetadataFilterList.js b/ui/src/app/metadata/editor/MetadataFilterList.js index a67ba450d..9b71f6ee5 100644 --- a/ui/src/app/metadata/editor/MetadataFilterList.js +++ b/ui/src/app/metadata/editor/MetadataFilterList.js @@ -26,7 +26,7 @@ export function MetadataFilterList() { const current = React.useContext(MetadataObjectContext); const onNavigate = (path) => { - history.push(path) + history.push(`../edit/${path}`); }; return ( diff --git a/ui/src/app/metadata/hoc/MetadataFilterSelector.js b/ui/src/app/metadata/hoc/MetadataFilterSelector.js index 3ac6e3ce0..79d422db1 100644 --- a/ui/src/app/metadata/hoc/MetadataFilterSelector.js +++ b/ui/src/app/metadata/hoc/MetadataFilterSelector.js @@ -33,10 +33,14 @@ export function MetadataFilterSelector({ children }) { return ( {filter && filter.version && - {children(filter)} + {children} } ); } +export function useMetadataFilterObject () { + return React.useContext(MetadataFilterContext); +} + export default MetadataFilterSelector; \ No newline at end of file diff --git a/ui/src/app/metadata/hoc/MetadataSelector.js b/ui/src/app/metadata/hoc/MetadataSelector.js index b4d5ec029..5efd8e460 100644 --- a/ui/src/app/metadata/hoc/MetadataSelector.js +++ b/ui/src/app/metadata/hoc/MetadataSelector.js @@ -4,6 +4,7 @@ import { useMetadataEntity } from '../hooks/api'; export const MetadataTypeContext = React.createContext(); export const MetadataObjectContext = React.createContext(); +export const MetadataLoaderContext = React.createContext(); /*eslint-disable react-hooks/exhaustive-deps*/ export function MetadataSelector({ children, ...props }) { @@ -33,12 +34,16 @@ export function MetadataSelector({ children, ...props }) { } React.useEffect(() => { loadMetadata(id) }, [id]); + const update = () => loadMetadata(id); + return ( <> {type && {metadata && metadata.version && - {children(metadata)} + + {children(metadata)} + } } @@ -46,4 +51,12 @@ export function MetadataSelector({ children, ...props }) { ); } +export function useMetadataObject () { + return React.useContext(MetadataObjectContext); +} + +export function useMetadataLoader () { + return React.useContext(MetadataLoaderContext); +} + export default MetadataSelector; \ No newline at end of file diff --git a/ui/src/app/metadata/hooks/api.js b/ui/src/app/metadata/hooks/api.js index f18e8c835..c799d4be4 100644 --- a/ui/src/app/metadata/hooks/api.js +++ b/ui/src/app/metadata/hooks/api.js @@ -1,6 +1,7 @@ import useFetch from 'use-http'; import API_BASE_PATH from '../../App.constant'; +import { useContentionDispatcher, openContentionModalAction } from '../contention/ContentionContext'; import {MetadataFilterTypes} from '../domain/filter'; @@ -83,4 +84,35 @@ export function useMetadataProviderTypes(opts = {}, onMount = null) { export function useMetadataFilterTypes () { return MetadataFilterTypes; +} + +export function useMetadataUpdater (path, current) { + const { request, put, get, error, response, ...props } = useFetch(path, { + cachePolicy: 'no-cache' + }); + + const dispatch = useContentionDispatcher(); + + async function update (p, body) { + const req = await put(p, body); + if (response.status === 409) { + const latest = await get(p); + return new Promise((resolve, reject) => { + dispatch(openContentionModalAction(current, latest, body, async (resolution) => { + resolve(await update(p, resolution)); + }, () => { + reject(); + })); + }); + } + return Promise.resolve(req); + } + + return { + ...props, + request, + response, + update, + error + } } \ No newline at end of file diff --git a/ui/src/app/metadata/view/EditFilter.js b/ui/src/app/metadata/view/EditFilter.js index fd38511e9..d5fd9f964 100644 --- a/ui/src/app/metadata/view/EditFilter.js +++ b/ui/src/app/metadata/view/EditFilter.js @@ -6,23 +6,26 @@ import Translate from '../../i18n/components/translate'; import { MetadataFilterEditor } from '../editor/MetadataFilterEditor'; import { MetadataForm } from '../hoc/MetadataFormContext'; import { MetadataSchema } from '../hoc/MetadataSchema'; -import { useMetadataFilters } from '../hooks/api'; -import { MetadataFilterSelector } from '../hoc/MetadataFilterSelector'; +import { getMetadataPath, useMetadataUpdater } from '../hooks/api'; +import { useMetadataFilterObject } from '../hoc/MetadataFilterSelector'; +import API_BASE_PATH from '../../App.constant'; export function EditFilter() { const { id, filterId } = useParams(); + const filter = useMetadataFilterObject(); const history = useHistory(); - const { put, response, loading } = useMetadataFilters(id, {}); + const { update, loading } = useMetadataUpdater(`${API_BASE_PATH}${getMetadataPath('provider')}/${id}/Filters/${filterId}`, filter); const [blocking, setBlocking] = React.useState(false); - async function save(metadata) { - await put(`${filterId}`, metadata); - if (response.ok) { + function save(metadata) { + update(``, metadata).then(() => { gotoDetail({ refresh: true }); - } + }).catch(() => { + window.location.reload(); + }); }; const cancel = () => { @@ -47,44 +50,38 @@ export function EditFilter() { Edit filter - - {(filter) => { - return ( - - - - - - Filter Type - - + + + + + + Filter Type + - - - {(filter, isInvalid) => - - save(filter)} - disabled={isInvalid || loading} - aria-label="Save changes to the metadata source. You will return to the dashboard"> - + + + + {(filter, isInvalid) => + + save(filter)} + disabled={isInvalid || loading} + aria-label="Save changes to the metadata source. You will return to the dashboard"> + Save - - cancel()} aria-label="Cancel changes, go back to dashboard"> - Cancel - - - } - - - - - ); - }} - + + cancel()} aria-label="Cancel changes, go back to dashboard"> + Cancel + + + } + + + + diff --git a/ui/src/app/metadata/view/MetadataEdit.js b/ui/src/app/metadata/view/MetadataEdit.js index cca87a56a..a3ff4d7e9 100644 --- a/ui/src/app/metadata/view/MetadataEdit.js +++ b/ui/src/app/metadata/view/MetadataEdit.js @@ -1,10 +1,14 @@ import React from 'react'; import { MetadataForm } from '../hoc/MetadataFormContext'; import { MetadataEditor } from '../editor/MetadataEditor'; +import { useMetadataObject } from '../hoc/MetadataSelector'; export function MetadataEdit() { + + const base = useMetadataObject(); + return ( - + ); diff --git a/ui/src/app/metadata/wizard/MetadataFilterTypeSelector.js b/ui/src/app/metadata/wizard/MetadataFilterTypeSelector.js index a634c7f25..f582f2eb9 100644 --- a/ui/src/app/metadata/wizard/MetadataFilterTypeSelector.js +++ b/ui/src/app/metadata/wizard/MetadataFilterTypeSelector.js @@ -13,7 +13,7 @@ export function MetadataFilterTypeSelector({ types = [], children, actions}) { mode: 'onChange', reValidateMode: 'onChange', defaultValues: { - type: 'NameIDFormat' + type: null }, resolver: undefined, context: undefined, diff --git a/ui/src/app/metadata/wizard/MetadataProviderWizard.js b/ui/src/app/metadata/wizard/MetadataProviderWizard.js index 776873747..5f612bfb4 100644 --- a/ui/src/app/metadata/wizard/MetadataProviderWizard.js +++ b/ui/src/app/metadata/wizard/MetadataProviderWizard.js @@ -44,13 +44,13 @@ export function MetadataProviderWizard({onRestart}) { }; const onBlur = (form) => { - console.log(form); + // console.log(form); } const validator = definition.validator(data); async function save() { - const body = removeNull(metadata, true); + const body = removeNull(definition.parser(metadata), true); await post('', body); if (response.ok) { history.push('/dashboard/metadata/manager/providers'); diff --git a/ui/src/app/metadata/wizard/MetadataSourceWizard.js b/ui/src/app/metadata/wizard/MetadataSourceWizard.js index 529ecb32f..735c19e17 100644 --- a/ui/src/app/metadata/wizard/MetadataSourceWizard.js +++ b/ui/src/app/metadata/wizard/MetadataSourceWizard.js @@ -46,7 +46,7 @@ export function MetadataSourceWizard ({ onShowNav }) { }; const onBlur = (form) => { - console.log(form); + // console.log(form); } async function save () { @@ -61,8 +61,6 @@ export function MetadataSourceWizard ({ onShowNav }) { const validator = definition.validator(data); - console.log(errors, loading) - return ( <>
{JSON.stringify(theirs, null, 4)}
{JSON.stringify(ours, null, 4)}
+ A newer version of this metadata source has been saved. Below are a list of changes. You can use your changes or their changes. +
+ There was a problem saving due to a mismatched version. +