From 859c9f0bed4a679b4c9f4e90d710b933a59fa29a Mon Sep 17 00:00:00 2001 From: pkupczyk <piotr.kupczyk@id.ethz.ch> Date: Sun, 22 Dec 2019 15:44:29 +0100 Subject: [PATCH] SSDM-7583 : ObjectTypeForm - improve 'save' logic (e.g. allow usage of legacy/common property types and turn them into type specific property types only if needed) --- .../types/objectType/ObjectTypeFacade.js | 13 - .../types/objectType/ObjectTypeHandlerLoad.js | 9 +- .../types/objectType/ObjectTypeHandlerSave.js | 341 +++++++++--------- .../ObjectTypeParametersProperty.jsx | 40 +- .../objectType/ObjectTypeParametersType.jsx | 4 +- ...UsageWarning.jsx => ObjectTypeWarning.jsx} | 11 +- .../objectType/ObjectTypeWarningLegacy.jsx | 17 + .../objectType/ObjectTypeWarningUsage.jsx | 20 + 8 files changed, 258 insertions(+), 197 deletions(-) rename openbis_ng_ui/src/js/components/types/objectType/{ObjectTypeUsageWarning.jsx => ObjectTypeWarning.jsx} (63%) create mode 100644 openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarningLegacy.jsx create mode 100644 openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarningUsage.jsx diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeFacade.js b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeFacade.js index 21062db9eaa..e23dbd7b452 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeFacade.js +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeFacade.js @@ -21,19 +21,6 @@ export default class ObjectTypeFacade { }) } - loadPropertyTypes(typeId) { - const criteria = new dto.PropertyTypeSearchCriteria() - criteria.withCode().thatStartsWith(typeId + '.') - - const fo = new dto.PropertyTypeFetchOptions() - fo.withVocabulary() - fo.withMaterialType() - - return facade.searchPropertyTypes(criteria, fo).then(result => { - return result.objects - }) - } - loadUsages(typeId) { function createTypeUsedOperation(typeId) { const criteria = new dto.SampleSearchCriteria() diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeHandlerLoad.js b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeHandlerLoad.js index ea17fd2ac74..e6b3954679d 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeHandlerLoad.js +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeHandlerLoad.js @@ -33,10 +33,6 @@ export default class ObjectTypeHandlerLoad { usages: loadedUsages.type } - type.original = { - ...type - } - const sections = [] const properties = [] let currentSection = null @@ -86,6 +82,11 @@ export default class ObjectTypeHandlerLoad { properties.push(currentProperty) }) + type.original = { + ...type, + properties + } + this.setState(() => ({ type, properties, diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeHandlerSave.js b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeHandlerSave.js index 5fccc4714f9..efd7d1c416f 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeHandlerSave.js +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeHandlerSave.js @@ -44,8 +44,6 @@ export default class ObjectTypeHandlerSave { } prepareProperties(type, properties, sections) { - const propertyCodePrefix = type.code + '.' - const propertiesMap = properties.reduce((map, property) => { map[property.id] = property return map @@ -59,10 +57,6 @@ export default class ObjectTypeHandlerSave { let code = property.code if (code) { code = code.toUpperCase() - - if (!code.startsWith(propertyCodePrefix)) { - code = propertyCodePrefix + code - } } const newProperty = _.mapValues( @@ -81,162 +75,204 @@ export default class ObjectTypeHandlerSave { return results } - preparePropertyChanges(loadedPropertyTypes) { - const toDelete = {} - const toCreate = {} - const toUpdate = {} - - function isUpdatePossible(type, property) { - const typeVocabulary = type.vocabulary ? type.vocabulary.code : null - const typeMaterialType = type.materialType ? type.materialType.code : null - const originalPlugin = property.original ? property.original.plugin : null - return ( - type.dataType === property.dataType && - typeVocabulary === property.vocabulary && - typeMaterialType === property.materialType && - originalPlugin === property.plugin - ) + getTypePrefix() { + return this.type.code + '.' + } + + hasTypePrefix(property) { + return property.code && property.code.startsWith(this.getTypePrefix()) + } + + addTypePrefix(property) { + if (property.code && !this.hasTypePrefix(property)) { + return { + ...property, + code: this.getTypePrefix() + property.code + } + } else { + return property } + } - const propertiesMap = this.properties.reduce((map, property) => { - map[property.code] = property - return map - }, {}) + hasPropertyChanged(property, path) { + const originalValue = property.original + ? _.get(property.original, path) + : null + const currentValue = _.get(property, path) + return originalValue !== currentValue + } - const loadedPropertyTypesMap = loadedPropertyTypes.reduce( - (map, loadedProperty) => { - map[loadedProperty.code] = loadedProperty - return map - }, - {} + isPropertyTypeUpdateNeeded(property) { + return ( + this.hasPropertyChanged(property, 'dataType') || + this.hasPropertyChanged(property, 'vocabulary') || + this.hasPropertyChanged(property, 'materialType') || + this.hasPropertyChanged(property, 'plugin') || + this.hasPropertyChanged(property, 'label') || + this.hasPropertyChanged(property, 'description') || + this.hasPropertyChanged(property, 'schema') || + this.hasPropertyChanged(property, 'transformation') ) + } - this.properties.forEach(property => { - const loadedProperty = loadedPropertyTypesMap[property.code] - if (!loadedProperty) { - toCreate[property.code] = property - } - }) + isPropertyTypeUpdatePossible(property) { + if ( + this.hasPropertyChanged(property, 'dataType') || + this.hasPropertyChanged(property, 'vocabulary') || + this.hasPropertyChanged(property, 'materialType') || + this.hasPropertyChanged(property, 'plugin') + ) { + return false + } + return true + } - loadedPropertyTypes.forEach(loadedPropertyType => { - const property = propertiesMap[loadedPropertyType.code] + createOperations() { + const operations = [] + const assignments = [] + + if (this.type.original) { + this.type.original.properties.forEach(originalProperty => { + const property = _.find(this.properties, ['id', originalProperty.id]) + if (!property) { + if (this.hasTypePrefix(originalProperty)) { + operations.push( + this.deletePropertyAssignmentOperation(originalProperty) + ) + operations.push(this.deletePropertyTypeOperation(originalProperty)) + } else { + operations.push( + this.deletePropertyAssignmentOperation(originalProperty) + ) + } + } + }) + } - if (property) { - if (isUpdatePossible(loadedPropertyType, property)) { - toUpdate[property.code] = property + this.properties.forEach((property, index) => { + if (property.original) { + if (this.hasTypePrefix(property)) { + if (this.isPropertyTypeUpdateNeeded(property)) { + if (this.isPropertyTypeUpdatePossible(property)) { + operations.push(this.updatePropertyTypeOperation(property)) + } else { + operations.push(this.deletePropertyAssignmentOperation(property)) + operations.push(this.deletePropertyTypeOperation(property)) + operations.push(this.createPropertyTypeOperation(property)) + } + } + assignments.push(this.propertyAssignmentCreation(property, index)) } else { - toDelete[property.code] = property.code - toCreate[property.code] = property + if (this.isPropertyTypeUpdateNeeded(property)) { + const propertyWithPrefix = this.addTypePrefix(property) + operations.push(this.deletePropertyAssignmentOperation(property)) + operations.push( + this.createPropertyTypeOperation(propertyWithPrefix) + ) + assignments.push( + this.propertyAssignmentCreation(propertyWithPrefix, index) + ) + } else { + assignments.push(this.propertyAssignmentCreation(property, index)) + } } } else { - toDelete[loadedPropertyType.code] = loadedPropertyType.code + const propertyWithPrefix = this.addTypePrefix(property) + operations.push(this.createPropertyTypeOperation(propertyWithPrefix)) + assignments.push( + this.propertyAssignmentCreation(propertyWithPrefix, index) + ) } }) - return { toCreate, toUpdate, toDelete } - } + operations.push(this.updateTypeOperation(assignments)) - deletePropertyAssignmentsOperations(toDelete) { - const assignmentIds = [] + return operations + } - Object.values(toDelete).forEach(code => { - assignmentIds.push( - new dto.PropertyAssignmentPermId( - new dto.EntityTypePermId(this.type.code, dto.EntityKind.SAMPLE), - new dto.PropertyTypePermId(code) - ) - ) - }) + deletePropertyAssignmentOperation(property) { + const assignmentId = new dto.PropertyAssignmentPermId( + new dto.EntityTypePermId(this.type.code, dto.EntityKind.SAMPLE), + new dto.PropertyTypePermId(property.code) + ) const update = new dto.SampleTypeUpdate() update.setTypeId( new dto.EntityTypePermId(this.type.code, dto.EntityKind.SAMPLE) ) - update.getPropertyAssignments().remove(assignmentIds) - update.getPropertyAssignments().setForceRemovingAssignments(true) + update.getPropertyAssignments().remove([assignmentId]) + update + .getPropertyAssignments() + .setForceRemovingAssignments(property.usages > 0) - return [new dto.UpdateSampleTypesOperation([update])] + return new dto.UpdateSampleTypesOperation([update]) } - createUpdateDeletePropertyTypesOperations(toCreate, toUpdate, toDelete) { - const operations = [] - - Object.values(toDelete).forEach(code => { - const ids = [new dto.PropertyTypePermId(code)] - const options = new dto.PropertyTypeDeletionOptions() - options.setReason('deleted via ng_ui') - operations.push(new dto.DeletePropertyTypesOperation(ids, options)) - }) - - Object.values(toCreate).forEach(property => { - const creation = new dto.PropertyTypeCreation() - creation.setCode(property.code) - creation.setLabel(property.label) - creation.setDescription(property.description) - creation.setDataType(property.dataType) - creation.setSchema(property.schema) - creation.setTransformation(property.transformation) - - if ( - property.dataType === dto.DataType.CONTROLLEDVOCABULARY && - property.vocabulary - ) { - creation.setVocabularyId(new dto.VocabularyPermId(property.vocabulary)) - } - if ( - property.dataType === dto.DataType.MATERIAL && - property.materialType - ) { - creation.setMaterialTypeId( - new dto.EntityTypePermId( - property.materialType, - dto.EntityKind.MATERIAL - ) - ) - } - operations.push(new dto.CreatePropertyTypesOperation([creation])) - }) + createPropertyTypeOperation(property) { + const creation = new dto.PropertyTypeCreation() + creation.setCode(property.code) + creation.setLabel(property.label) + creation.setDescription(property.description) + creation.setDataType(property.dataType) + creation.setSchema(property.schema) + creation.setTransformation(property.transformation) + + if ( + property.dataType === dto.DataType.CONTROLLEDVOCABULARY && + property.vocabulary + ) { + creation.setVocabularyId(new dto.VocabularyPermId(property.vocabulary)) + } + if (property.dataType === dto.DataType.MATERIAL && property.materialType) { + creation.setMaterialTypeId( + new dto.EntityTypePermId(property.materialType, dto.EntityKind.MATERIAL) + ) + } + return new dto.CreatePropertyTypesOperation([creation]) + } - Object.values(toUpdate).forEach(property => { - const update = new dto.PropertyTypeUpdate() - if (property.code) { - update.setTypeId(new dto.PropertyTypePermId(property.code)) - } - update.setLabel(property.label) - update.setDescription(property.description) - update.setSchema(property.schema) - update.setTransformation(property.transformation) - operations.push(new dto.UpdatePropertyTypesOperation([update])) - }) + updatePropertyTypeOperation(property) { + const update = new dto.PropertyTypeUpdate() + if (property.code) { + update.setTypeId(new dto.PropertyTypePermId(property.code)) + } + update.setLabel(property.label) + update.setDescription(property.description) + update.setSchema(property.schema) + update.setTransformation(property.transformation) + return new dto.UpdatePropertyTypesOperation([update]) + } - return operations + deletePropertyTypeOperation(property) { + const id = new dto.PropertyTypePermId(property.code) + const options = new dto.PropertyTypeDeletionOptions() + options.setReason('deleted via ng_ui') + return new dto.DeletePropertyTypesOperation([id], options) } - updateTypeAndAssignmentsOperations() { - const updateProperties = this.properties.map((property, index) => { - let updateProperty = new dto.PropertyAssignmentCreation() - updateProperty.setOrdinal(index + 1) - updateProperty.setMandatory(property.mandatory) - updateProperty.setInitialValueForExistingEntities( - property.initialValueForExistingEntities - ) - updateProperty.setShowInEditView(property.showInEditView) - updateProperty.setShowRawValueInForms(property.showRawValueInForms) - updateProperty.setSection(property.section) + propertyAssignmentCreation(property, index) { + let creation = new dto.PropertyAssignmentCreation() + creation.setOrdinal(index + 1) + creation.setMandatory(property.mandatory) + creation.setInitialValueForExistingEntities( + property.initialValueForExistingEntities + ) + creation.setShowInEditView(property.showInEditView) + creation.setShowRawValueInForms(property.showRawValueInForms) + creation.setSection(property.section) - if (property.code) { - updateProperty.setPropertyTypeId( - new dto.PropertyTypePermId(property.code) - ) - } + if (property.code) { + creation.setPropertyTypeId(new dto.PropertyTypePermId(property.code)) + } - if (property.plugin) { - updateProperty.setPluginId(new dto.PluginPermId(property.plugin)) - } + if (property.plugin) { + creation.setPluginId(new dto.PluginPermId(property.plugin)) + } - return updateProperty - }) + return creation + } + updateTypeOperation(assignments) { const update = new dto.SampleTypeUpdate() update.setTypeId( new dto.EntityTypePermId(this.type.code, dto.EntityKind.SAMPLE) @@ -254,9 +290,9 @@ export default class ObjectTypeHandlerSave { ? new dto.PluginPermId(this.type.validationPlugin) : null ) - update.getPropertyAssignments().set(updateProperties) + update.getPropertyAssignments().set(assignments) - return [new dto.UpdateSampleTypesOperation([update])] + return new dto.UpdateSampleTypesOperation([update]) } execute() { @@ -269,7 +305,13 @@ export default class ObjectTypeHandlerSave { this.setState({ loading: true }) - return this.doExecute() + + const operations = this.createOperations() + const options = new dto.SynchronousOperationExecutionOptions() + options.setExecuteInOrder(true) + + return this.facade + .executeOperations(operations, options) .then(() => { return this.validateHandler.setEnabled(false) }) @@ -286,35 +328,4 @@ export default class ObjectTypeHandlerSave { }) }) } - - doExecute() { - return this.facade - .loadPropertyTypes(this.type.code) - .then(loadedPropertyTypes => { - const { toCreate, toUpdate, toDelete } = this.preparePropertyChanges( - loadedPropertyTypes - ) - const deletePropertyAssignmentsOperations = this.deletePropertyAssignmentsOperations( - toDelete - ) - const createUpdateDeletePropertyTypesOperations = this.createUpdateDeletePropertyTypesOperations( - toCreate, - toUpdate, - toDelete - ) - const updateTypeAndAssignmentsOperations = this.updateTypeAndAssignmentsOperations() - - const options = new dto.SynchronousOperationExecutionOptions() - options.setExecuteInOrder(true) - - return this.facade.executeOperations( - [ - ...deletePropertyAssignmentsOperations, - ...createUpdateDeletePropertyTypesOperations, - ...updateTypeAndAssignmentsOperations - ], - options - ) - }) - } } diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx index 6d6a6ad7f86..62a578f99ec 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersProperty.jsx @@ -4,7 +4,8 @@ import Typography from '@material-ui/core/Typography' import CheckboxField from '../../common/form/CheckboxField.jsx' import TextField from '../../common/form/TextField.jsx' import SelectField from '../../common/form/SelectField.jsx' -import ObjectTypeUsageWarning from './ObjectTypeUsageWarning.jsx' +import ObjectTypeWarningUsage from './ObjectTypeWarningUsage.jsx' +import ObjectTypeWarningLegacy from './ObjectTypeWarningLegacy.jsx' import { dto } from '../../../services/openbis.js' import logger from '../../../common/logger.js' @@ -176,7 +177,8 @@ class ObjectTypeParametersProperty extends React.PureComponent { <Typography variant='h6' className={classes.header}> Property </Typography> - {this.renderWarning(property)} + {this.renderWarningLegacy(property)} + {this.renderWarningUsage(property)} {this.renderCode(property)} {this.renderDataType(property)} {this.renderVocabulary(property)} @@ -193,12 +195,25 @@ class ObjectTypeParametersProperty extends React.PureComponent { ) } - renderWarning(property) { + renderWarningLegacy(property) { + if (this.isLegacy(property)) { + const { classes } = this.props + return ( + <div className={classes.field}> + <ObjectTypeWarningLegacy /> + </div> + ) + } else { + return null + } + } + + renderWarningUsage(property) { if (property.usages > 0) { const { classes } = this.props return ( <div className={classes.field}> - <ObjectTypeUsageWarning subject='property' usages={property.usages} /> + <ObjectTypeWarningUsage subject='property' usages={property.usages} /> </div> ) } else { @@ -216,6 +231,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { name='label' mandatory={true} error={property.errors.label} + disabled={this.isLegacy(property)} value={property.label} onChange={this.handleChange} onFocus={this.handleFocus} @@ -255,6 +271,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { name='description' mandatory={true} error={property.errors.description} + disabled={this.isLegacy(property)} value={property.description} onChange={this.handleChange} onFocus={this.handleFocus} @@ -282,7 +299,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { name='dataType' mandatory={true} error={property.errors.dataType} - disabled={property.usages > 0} + disabled={property.usages > 0 || this.isLegacy(property)} value={property.dataType} options={options} onChange={this.handleChange} @@ -318,7 +335,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { name='vocabulary' mandatory={true} error={property.errors.vocabulary} - disabled={property.usages > 0} + disabled={property.usages > 0 || this.isLegacy(property)} value={property.vocabulary} options={options} onChange={this.handleChange} @@ -357,7 +374,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { name='materialType' mandatory={true} error={property.errors.materialType} - disabled={property.usages > 0} + disabled={property.usages > 0 || this.isLegacy(property)} value={property.materialType} options={options} onChange={this.handleChange} @@ -381,6 +398,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { label='XML Schema' name='schema' error={property.errors.schema} + disabled={this.isLegacy(property)} value={property.schema} multiline={true} onChange={this.handleChange} @@ -404,6 +422,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { label='XSLT Script' name='transformation' error={property.errors.transformation} + disabled={this.isLegacy(property)} value={property.transformation} multiline={true} onChange={this.handleChange} @@ -440,6 +459,7 @@ class ObjectTypeParametersProperty extends React.PureComponent { label='Dynamic Plugin' name='plugin' error={property.errors.plugin} + disabled={property.usages > 0} value={property.plugin} options={options} onChange={this.handleChange} @@ -535,6 +555,12 @@ class ObjectTypeParametersProperty extends React.PureComponent { return null } } + + isLegacy(property) { + return ( + property.original && !property.code.startsWith(this.props.type.code + '.') + ) + } } export default withStyles(styles)(ObjectTypeParametersProperty) diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersType.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersType.jsx index 0a5b20a4b5c..9a4aff70cf1 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersType.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeParametersType.jsx @@ -4,7 +4,7 @@ import Typography from '@material-ui/core/Typography' import CheckboxField from '../../common/form/CheckboxField.jsx' import TextField from '../../common/form/TextField.jsx' import SelectField from '../../common/form/SelectField.jsx' -import ObjectTypeUsageWarning from './ObjectTypeUsageWarning.jsx' +import ObjectTypeWarningUsage from './ObjectTypeWarningUsage.jsx' import logger from '../../../common/logger.js' const styles = theme => ({ @@ -144,7 +144,7 @@ class ObjectTypeParametersType extends React.PureComponent { const { classes } = this.props return ( <div className={classes.field}> - <ObjectTypeUsageWarning subject='type' usages={type.usages} /> + <ObjectTypeWarningUsage subject='type' usages={type.usages} /> </div> ) } else { diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeUsageWarning.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarning.jsx similarity index 63% rename from openbis_ng_ui/src/js/components/types/objectType/ObjectTypeUsageWarning.jsx rename to openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarning.jsx index e12f33ea854..79b98e7a8fa 100644 --- a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeUsageWarning.jsx +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarning.jsx @@ -15,20 +15,19 @@ const styles = theme => ({ } }) -class ObjectTypeUsageWarning extends React.PureComponent { +class ObjectTypeWarning extends React.PureComponent { render() { - logger.log(logger.DEBUG, 'ObjectTypeUsageWarning.render') + logger.log(logger.DEBUG, 'ObjectTypeWarning.render') - const { classes, subject, usages } = this.props + const { classes, children } = this.props return ( <Typography variant='body2' className={classes.warning}> <WarningIcon /> - This {subject} is already used by {usages} - {usages > 1 ? ' entities' : ' entity'}. + {children} </Typography> ) } } -export default withStyles(styles)(ObjectTypeUsageWarning) +export default withStyles(styles)(ObjectTypeWarning) diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarningLegacy.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarningLegacy.jsx new file mode 100644 index 00000000000..358c2f57b14 --- /dev/null +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarningLegacy.jsx @@ -0,0 +1,17 @@ +import React from 'react' +import ObjectTypeWarning from './ObjectTypeWarning.jsx' +import logger from '../../../common/logger.js' + +class ObjectTypeWarningLegacy extends React.PureComponent { + render() { + logger.log(logger.DEBUG, 'ObjectTypeWarningLegacy.render') + + return ( + <ObjectTypeWarning> + This property is legacy (reusable among types). + </ObjectTypeWarning> + ) + } +} + +export default ObjectTypeWarningLegacy diff --git a/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarningUsage.jsx b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarningUsage.jsx new file mode 100644 index 00000000000..b236d160bc9 --- /dev/null +++ b/openbis_ng_ui/src/js/components/types/objectType/ObjectTypeWarningUsage.jsx @@ -0,0 +1,20 @@ +import React from 'react' +import ObjectTypeWarning from './ObjectTypeWarning.jsx' +import logger from '../../../common/logger.js' + +class ObjectTypeWarningUsage extends React.PureComponent { + render() { + logger.log(logger.DEBUG, 'ObjectTypeWarningUsage.render') + + const { subject, usages } = this.props + + return ( + <ObjectTypeWarning> + This {subject} is already used by {usages} + {usages > 1 ? ' entities' : ' entity'}. + </ObjectTypeWarning> + ) + } +} + +export default ObjectTypeWarningUsage -- GitLab