diff --git a/docs/configuration/FieldConfiguration.md b/docs/configuration/FieldConfiguration.md index b235e677a..3c2424910 100644 --- a/docs/configuration/FieldConfiguration.md +++ b/docs/configuration/FieldConfiguration.md @@ -10,7 +10,7 @@ type FieldDescriptor = { ``` ``` type FieldConfig = { - cloneable: boolean, + cloneable: boolean | function, defaultValue: Immutable.Map | Immutable.List | string, dataType: string, messages: MessageDescriptorMap, @@ -98,10 +98,17 @@ The field configuration object may contain the following properties: ### cloneable ``` -cloneable: boolean = true +required: boolean | (computeContext) => boolean ``` If true, when a record is cloned, the value of this field is cloned to the new record. Otherwise, the field in the new record will be set to the default value if one exists; otherwise, it will be empty. +If a function is provided, the function will be called with a computeContext object as an argument, and must return a boolean.The computeContext has the following properties: + +| Property | Type | Description | +| ----------------- | -------------- | ----------- | +| `form` | String | The name of the form (template) that is in use. | +| `roleNames` | Immutable.List | The current user's roles. | + ### defaultValue ``` defaultValue: Array | Object | string | number | boolean diff --git a/src/actions/partialTermSearch.js b/src/actions/partialTermSearch.js index 32a464fb5..e1db4cd68 100644 --- a/src/actions/partialTermSearch.js +++ b/src/actions/partialTermSearch.js @@ -33,7 +33,7 @@ export const addTerm = (recordTypeConfig, vocabulary, displayName, partialTerm, vocabulary, }, }); - + let newRecordData = Immutable.Map(); if (clone) { diff --git a/src/actions/record.js b/src/actions/record.js index a121b4dd7..b2048d342 100644 --- a/src/actions/record.js +++ b/src/actions/record.js @@ -533,6 +533,7 @@ export const readRecord = (config, recordTypeConfig, vocabularyConfig, csid, opt export const createNewRecord = (config, recordTypeConfig, vocabularyConfig, cloneCsid) => ( (dispatch, getState) => { let readClone; + const state = getState(); if (cloneCsid) { const data = getRecordData(getState(), cloneCsid); @@ -555,6 +556,11 @@ export const createNewRecord = (config, recordTypeConfig, vocabularyConfig, clon }); } + const computeContext = { + form: getForm(state, recordTypeConfig.name), + roleNames: getUserRoleNames(state), + }; + return ( readClone.then(() => dispatch({ type: CREATE_NEW_RECORD, @@ -562,7 +568,8 @@ export const createNewRecord = (config, recordTypeConfig, vocabularyConfig, clon config, recordTypeConfig, cloneCsid, - stickyFields: getStickyFields(getState()), + stickyFields: getStickyFields(state), + computeContext, }, })) ); @@ -610,6 +617,7 @@ export const createNewSubrecord = ( cloneCsid, isDefault, stickyFields: getStickyFields(getState()), + form: getForm(getState()), }, })) ); diff --git a/src/helpers/configHelpers.js b/src/helpers/configHelpers.js index 09744258f..19ed38970 100644 --- a/src/helpers/configHelpers.js +++ b/src/helpers/configHelpers.js @@ -490,13 +490,19 @@ export const isAutocompleteField = (fieldDescriptor) => { return (JSON.stringify(viewType) === '"AutocompleteInput"'); }; -export const isFieldCloneable = (fieldDescriptor) => { +export const isFieldCloneable = (fieldDescriptor, computeContext) => { const config = fieldDescriptor[configKey]; if (config && 'cloneable' in config) { - return config.cloneable; - } + // eslint-disable-next-line prefer-destructuring + let cloneable = config.cloneable; + + if (typeof (cloneable) === 'function') { + cloneable = cloneable(computeContext); + } + return !!cloneable; + } return true; }; diff --git a/src/helpers/recordDataHelpers.js b/src/helpers/recordDataHelpers.js index b09e29915..2cd06f310 100644 --- a/src/helpers/recordDataHelpers.js +++ b/src/helpers/recordDataHelpers.js @@ -350,12 +350,12 @@ export const createRecordData = (recordTypeConfig) => applyDefaults( * Clear uncloneable fields from record data. Existing (not undefined) values in fields that are * not cloneable are set to the default value if one exists, or undefined otherwise. */ -export const clearUncloneable = (fieldDescriptor, data) => { +export const clearUncloneable = (fieldDescriptor, data, computeContext) => { if (!fieldDescriptor) { return data; - } + } - if (typeof data !== 'undefined' && !isFieldCloneable(fieldDescriptor)) { + if (typeof data !== 'undefined' && !isFieldCloneable(fieldDescriptor, computeContext)) { // If the field has been configured as not cloneable and there is an existing value, replace // the existing value with the default value if there is one, or undefined otherwise. The old // UI did not set uncloneable fields to the default value, but I think this was an oversight. @@ -369,13 +369,13 @@ export const clearUncloneable = (fieldDescriptor, data) => { if (Immutable.Map.isMap(data)) { return data.reduce((updatedData, child, name) => updatedData.set( - name, clearUncloneable(fieldDescriptor[name], child), + name, clearUncloneable(fieldDescriptor[name], child, computeContext), ), data); } if (Immutable.List.isList(data)) { return data.reduce((updatedData, child, index) => updatedData.set( - index, clearUncloneable(fieldDescriptor, child), + index, clearUncloneable(fieldDescriptor, child, computeContext), ), data); } @@ -411,7 +411,7 @@ export const prepareClonedHierarchy = (fromCsid, data) => { /** * Create a new record as a clone of a given record. */ -export const cloneRecordData = (recordTypeConfig, csid, data) => { +export const cloneRecordData = (recordTypeConfig, csid, data, computeContext) => { if (!data) { return data; } @@ -424,8 +424,8 @@ export const cloneRecordData = (recordTypeConfig, csid, data) => { clone = clone.deleteIn(['document', `${NS_PREFIX}:account_permission`]); // Reset fields that are configured as not cloneable. + clone = clearUncloneable(recordTypeConfig.fields, clone, computeContext); - clone = clearUncloneable(recordTypeConfig.fields, clone); clone = prepareClonedHierarchy(csid, clone); return clone; diff --git a/src/reducers/record.js b/src/reducers/record.js index 89b6a2e34..ec5a2286d 100644 --- a/src/reducers/record.js +++ b/src/reducers/record.js @@ -157,7 +157,8 @@ const sortFieldInstances = (state, action) => { return setCurrentData(state, csid, updatedData); }; -const doCreateNew = (state, config, recordTypeConfig, options = {}) => { +const doCreateNew = (state, config, recordTypeConfig, computeContext, options = {}) => { + const { cloneCsid, subrecordName, @@ -167,7 +168,7 @@ const doCreateNew = (state, config, recordTypeConfig, options = {}) => { let data; if (cloneCsid) { - data = cloneRecordData(recordTypeConfig, cloneCsid, getCurrentData(state, cloneCsid)); + data = cloneRecordData(recordTypeConfig, cloneCsid, getCurrentData(state, cloneCsid), computeContext); } if (!data) { @@ -210,6 +211,7 @@ const doCreateNew = (state, config, recordTypeConfig, options = {}) => { nextState, config, subrecordTypeConfig, + computeContext, { cloneCsid: subrecordCsid, subrecordName: name, @@ -242,9 +244,10 @@ const createNewRecord = (state, action) => { recordTypeConfig, cloneCsid, stickyFields, + computeContext, } = action.meta; - return doCreateNew(state, config, recordTypeConfig, { + return doCreateNew(state, config, recordTypeConfig, computeContext, { cloneCsid, stickyFields, }); @@ -519,9 +522,11 @@ const createNewSubrecord = (state, action) => { cloneCsid, isDefault, stickyFields, + computeContext, + form, } = action.meta; - let nextState = doCreateNew(state, config, subrecordTypeConfig, { + let nextState = doCreateNew(state, config, subrecordTypeConfig, computeContext, { cloneCsid, subrecordName, stickyFields, diff --git a/test/specs/actions/record.spec.js b/test/specs/actions/record.spec.js index 67fd67648..5abec9877 100644 --- a/test/specs/actions/record.spec.js +++ b/test/specs/actions/record.spec.js @@ -118,6 +118,8 @@ describe('record action creator', () => { it('should dispatch CREATE_NEW_RECORD', () => { const store = mockStore({ prefs: Immutable.Map(), + user: Immutable.Map(), + authz: Immutable.Map(), }); const config = { @@ -134,6 +136,11 @@ describe('record action creator', () => { const cloneCsid = undefined; + const computeContext = { + form: undefined, + roleNames: undefined, + }; + return store.dispatch(createNewRecord(config, recordTypeConfig, vocabularyConfig, cloneCsid)) .then(() => { const actions = store.getActions(); @@ -147,6 +154,7 @@ describe('record action creator', () => { recordTypeConfig, cloneCsid, stickyFields: undefined, + computeContext, }, }); }); @@ -154,7 +162,6 @@ describe('record action creator', () => { it('should read the record to be cloned', () => { const servicePath = 'collectionobjects'; - const config = { foo: 'abc', }; @@ -177,8 +184,15 @@ describe('record action creator', () => { const store = mockStore({ prefs: Immutable.Map(), record: Immutable.Map(), + user: Immutable.Map(), + authz: Immutable.Map(), }); + const computeContext = { + form: undefined, + roleNames: undefined, + }; + return store.dispatch(createNewRecord(config, recordTypeConfig, vocabularyConfig, cloneCsid)) .then(() => { const actions = store.getActions(); @@ -215,6 +229,7 @@ describe('record action creator', () => { recordTypeConfig, cloneCsid, stickyFields: undefined, + computeContext, }, }); }); @@ -243,6 +258,8 @@ describe('record action creator', () => { it('should dispatch CREATE_NEW_SUBRECORD', () => { const store = mockStore({ prefs: Immutable.Map(), + user: Immutable.Map(), + authz: Immutable.Map(), }); const config = { @@ -264,6 +281,11 @@ describe('record action creator', () => { const cloneCsid = undefined; const isDefault = true; + // const computeContext = { + // form: undefined, + // roleNames: undefined, + // }; + return store.dispatch(createNewSubrecord( config, csid, csidField, subrecordName, subrecordTypeConfig, subrecordVocabularyConfig, cloneCsid, isDefault, @@ -284,6 +306,7 @@ describe('record action creator', () => { cloneCsid, isDefault, stickyFields: undefined, + form: undefined, }, }); }); @@ -293,6 +316,9 @@ describe('record action creator', () => { const store = mockStore({ prefs: Immutable.Map(), record: Immutable.Map(), + user: Immutable.Map(), + authz: Immutable.Map(), + // form: Immutable.Map(), }); const config = { @@ -364,6 +390,7 @@ describe('record action creator', () => { cloneCsid, isDefault, stickyFields: undefined, + form: undefined, }, }); }); diff --git a/test/specs/components/sections/Footer.spec.jsx b/test/specs/components/sections/Footer.spec.jsx index 58ca6eac3..daa4c057b 100644 --- a/test/specs/components/sections/Footer.spec.jsx +++ b/test/specs/components/sections/Footer.spec.jsx @@ -56,7 +56,7 @@ describe('Footer', () => { const lists = this.container.querySelectorAll('ul'); const items = lists[1].querySelectorAll('li'); - items[0].textContent.should.equal('Release 5.1'); + items[0].textContent.should.equal('Release 5.1-1'); }); it('should render no version number if it is not present in system info', function test() { @@ -120,6 +120,6 @@ describe('Footer', () => { const lists = this.container.querySelectorAll('ul'); const items = lists[1].querySelectorAll('li'); - items[2].textContent.should.equal('formatted somePlugin.name version 1.2.3'); + items[2].textContent.should.equal('UC Berkeley formatted somePlugin.name version 1.2.3'); }); }); diff --git a/test/specs/containers/invocable/InvocationEditorContainer.spec.jsx b/test/specs/containers/invocable/InvocationEditorContainer.spec.jsx index 723f7982a..5eff500ce 100644 --- a/test/specs/containers/invocable/InvocationEditorContainer.spec.jsx +++ b/test/specs/containers/invocable/InvocationEditorContainer.spec.jsx @@ -32,6 +32,7 @@ const store = mockStore({ }, }, }), + user: Immutable.Map(), }); const context = {