From 828d8f13578c057ed64a4d308a6d17fc1b643527 Mon Sep 17 00:00:00 2001 From: pkupczyk <piotr.kupczyk@id.ethz.ch> Date: Wed, 16 Feb 2022 11:43:53 +0100 Subject: [PATCH] SSDM-11169 : Properties overview in the new admin UI - property type usages --- openbis_ng_ui/src/js/common/messages.js | 4 +- .../{TypesGrid.jsx => EntityTypesGrid.jsx} | 6 +- .../types/common/PropertyTypesGrid.jsx | 28 ++-- .../common/PropertyTypesGridUsagesCell.jsx | 111 ++++++++++++++++ ...lariesGrid.jsx => VocabularyTypesGrid.jsx} | 6 +- .../js/components/types/search/TypeSearch.jsx | 122 +++++++++++++++--- 6 files changed, 234 insertions(+), 43 deletions(-) rename openbis_ng_ui/src/js/components/types/common/{TypesGrid.jsx => EntityTypesGrid.jsx} (96%) create mode 100644 openbis_ng_ui/src/js/components/types/common/PropertyTypesGridUsagesCell.jsx rename openbis_ng_ui/src/js/components/types/common/{VocabulariesGrid.jsx => VocabularyTypesGrid.jsx} (89%) diff --git a/openbis_ng_ui/src/js/common/messages.js b/openbis_ng_ui/src/js/common/messages.js index 91f669fe320..b69e5dc8364 100644 --- a/openbis_ng_ui/src/js/common/messages.js +++ b/openbis_ng_ui/src/js/common/messages.js @@ -154,7 +154,6 @@ const keys = { PROPERTY_IS_NOT_USED: 'PROPERTY_IS_NOT_USED', PROPERTY_IS_USED: 'PROPERTY_IS_USED', PROPERTY_PARAMETERS_CANNOT_BE_CHANGED: 'PROPERTY_PARAMETERS_CANNOT_BE_CHANGED', - PROPERTY_TYPE: 'PROPERTY_TYPE', PROPERTY_TYPES: 'PROPERTY_TYPES', PUBLIC: 'PUBLIC', QUERIES: 'QUERIES', @@ -207,6 +206,7 @@ const keys = { UNSAVED_CHANGES: 'UNSAVED_CHANGES', UPDATE_MODE: "UPDATE_MODE", URL_TEMPLATE: 'URL_TEMPLATE', + USAGES: 'USAGES', USER: 'USER', USERS: 'USERS', USERS_WHO_REGISTERED_SOME_DATA_CANNOT_BE_REMOVED: 'USERS_WHO_REGISTERED_SOME_DATA_CANNOT_BE_REMOVED', @@ -385,7 +385,6 @@ const messages_en = { [keys.PROPERTY_IS_NOT_USED]: 'This property assignment is not yet used by any entities of "${0}" type.', [keys.PROPERTY_IS_USED]: 'This property assignment is already used by existing entities of "${0}" type. Removing it is also going to remove ${1} existing property value(s) - data will be lost! Are you sure you want to proceed?', [keys.PROPERTY_PARAMETERS_CANNOT_BE_CHANGED]: 'The property parameters cannot be changed.', - [keys.PROPERTY_TYPE]: 'Property Type', [keys.PROPERTY_TYPES]: 'Property Types', [keys.PUBLIC]: 'Public', [keys.QUERIES]: 'Queries', @@ -438,6 +437,7 @@ const messages_en = { [keys.UNSAVED_CHANGES]: 'You have unsaved changes', [keys.UPDATE_MODE]: 'Update Mode', [keys.URL_TEMPLATE]: 'URL Template', + [keys.USAGES]: 'Usages', [keys.USERS]: 'Users', [keys.USERS_WHO_REGISTERED_SOME_DATA_CANNOT_BE_REMOVED]: 'Users who have already registered some data cannot be removed.', [keys.USER]: 'User', diff --git a/openbis_ng_ui/src/js/components/types/common/TypesGrid.jsx b/openbis_ng_ui/src/js/components/types/common/EntityTypesGrid.jsx similarity index 96% rename from openbis_ng_ui/src/js/components/types/common/TypesGrid.jsx rename to openbis_ng_ui/src/js/components/types/common/EntityTypesGrid.jsx index c9c2d58a459..d634dddded5 100644 --- a/openbis_ng_ui/src/js/components/types/common/TypesGrid.jsx +++ b/openbis_ng_ui/src/js/components/types/common/EntityTypesGrid.jsx @@ -6,9 +6,9 @@ import openbis from '@src/js/services/openbis.js' import messages from '@src/js/common/messages.js' import logger from '@src/js/common/logger.js' -class TypesGrid extends React.PureComponent { +class EntityTypesGrid extends React.PureComponent { render() { - logger.log(logger.DEBUG, 'TypesGrid.render') + logger.log(logger.DEBUG, 'EntityTypesGrid.render') const { id, rows, selectedRowId, onSelectedRowChange, controllerRef } = this.props @@ -119,4 +119,4 @@ class TypesGrid extends React.PureComponent { } } -export default TypesGrid +export default EntityTypesGrid diff --git a/openbis_ng_ui/src/js/components/types/common/PropertyTypesGrid.jsx b/openbis_ng_ui/src/js/components/types/common/PropertyTypesGrid.jsx index 55044791ab7..3171151df78 100644 --- a/openbis_ng_ui/src/js/components/types/common/PropertyTypesGrid.jsx +++ b/openbis_ng_ui/src/js/components/types/common/PropertyTypesGrid.jsx @@ -1,7 +1,6 @@ -import _ from 'lodash' import React from 'react' import GridWithSettings from '@src/js/components/common/grid/GridWithSettings.jsx' -import PropertyTypeLink from '@src/js/components/common/link/PropertyTypeLink.jsx' +import PropertyTypesGridUsagesCell from '@src/js/components/types/common/PropertyTypesGridUsagesCell.jsx' import messages from '@src/js/common/messages.js' import logger from '@src/js/common/logger.js' @@ -21,10 +20,7 @@ class PropertyTypesGrid extends React.PureComponent { { name: 'code', label: messages.get(messages.CODE), - getValue: ({ row }) => row.code, - renderValue: ({ row }) => { - return <PropertyTypeLink propertyTypeCode={row.code} /> - } + getValue: ({ row }) => row.code }, { name: 'label', @@ -67,18 +63,18 @@ class PropertyTypesGrid extends React.PureComponent { getValue: ({ row }) => row.schema }, { - name: 'metaData', - label: messages.get(messages.META_DATA), + name: 'usages', + label: messages.get(messages.USAGES), getValue: ({ row }) => - _.isEmpty(row.metaData) ? null : JSON.stringify(row.metaData), - renderValue: ({ value }) => { - return <pre>{value}</pre> + row.usages ? JSON.stringify(row.usages) : null, + compareValue: ({ row1, row2, defaultCompare }) => { + const value1 = row1.usages ? row1.usages.count : 0 + const value2 = row2.usages ? row2.usages.count : 0 + return defaultCompare(value1, value2) + }, + renderValue: ({ row }) => { + return <PropertyTypesGridUsagesCell value={row.usages} /> } - }, - { - name: 'internal', - label: messages.get(messages.INTERNAL), - getValue: ({ row }) => row.internal } ]} rows={rows} diff --git a/openbis_ng_ui/src/js/components/types/common/PropertyTypesGridUsagesCell.jsx b/openbis_ng_ui/src/js/components/types/common/PropertyTypesGridUsagesCell.jsx new file mode 100644 index 00000000000..88b95ef7923 --- /dev/null +++ b/openbis_ng_ui/src/js/components/types/common/PropertyTypesGridUsagesCell.jsx @@ -0,0 +1,111 @@ +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import Collapse from '@material-ui/core/Collapse' +import Link from '@material-ui/core/Link' +import TypeLink from '@src/js/components/common/link/TypeLink.jsx' +import openbis from '@src/js/services/openbis.js' +import messages from '@src/js/common/messages.js' +import logger from '@src/js/common/logger.js' + +const styles = theme => ({ + usages: { + padding: 0, + margin: 0, + marginTop: theme.spacing(1) + }, + usage: { + listStyle: 'none', + margin: 0, + padding: 0 + } +}) + +class PropertyTypesGridUsagesCell extends React.PureComponent { + constructor(props) { + super(props) + this.state = { + visible: false + } + this.handleVisibilityChange = this.handleVisibilityChange.bind(this) + } + + handleVisibilityChange() { + this.setState(state => ({ + visible: !state.visible + })) + } + + render() { + logger.log(logger.DEBUG, 'PropertyTypesGridUsagesCell.render') + + const { value } = this.props + const { visible } = this.state + + if (value) { + return ( + <div> + <div> + {value.count} ( + <Link + onClick={() => { + this.handleVisibilityChange() + }} + > + {visible + ? messages.get(messages.HIDE) + : messages.get(messages.SHOW)} + </Link> + ) + </div> + <Collapse in={visible} mountOnEnter={true} unmountOnExit={true}> + <div> + {this.renderUsages( + openbis.EntityKind.EXPERIMENT, + messages.get(messages.COLLECTION_TYPES), + value.experimentTypes + )} + {this.renderUsages( + openbis.EntityKind.SAMPLE, + messages.get(messages.OBJECT_TYPES), + value.sampleTypes + )} + {this.renderUsages( + openbis.EntityKind.DATA_SET, + messages.get(messages.DATA_SET_TYPES), + value.dataSetTypes + )} + {this.renderUsages( + openbis.EntityKind.MATERIAL, + messages.get(messages.MATERIAL_TYPES), + value.materialTypes + )} + </div> + </Collapse> + </div> + ) + } else { + return 0 + } + } + + renderUsages(usageKind, usagesHeader, usagesList) { + if (usagesList.length === 0) { + return null + } + + const { classes } = this.props + + return ( + <ul className={classes.usages}> + {usagesHeader}: + {usagesList.map(usage => ( + <li key={usage} className={classes.usage}> + <TypeLink typeKind={usageKind} typeCode={usage} /> + </li> + ))} + </ul> + ) + } +} + +export default withStyles(styles)(PropertyTypesGridUsagesCell) diff --git a/openbis_ng_ui/src/js/components/types/common/VocabulariesGrid.jsx b/openbis_ng_ui/src/js/components/types/common/VocabularyTypesGrid.jsx similarity index 89% rename from openbis_ng_ui/src/js/components/types/common/VocabulariesGrid.jsx rename to openbis_ng_ui/src/js/components/types/common/VocabularyTypesGrid.jsx index ab7ee679c7a..776479c561a 100644 --- a/openbis_ng_ui/src/js/components/types/common/VocabulariesGrid.jsx +++ b/openbis_ng_ui/src/js/components/types/common/VocabularyTypesGrid.jsx @@ -4,9 +4,9 @@ import VocabularyLink from '@src/js/components/common/link/VocabularyLink.jsx' import messages from '@src/js/common/messages.js' import logger from '@src/js/common/logger.js' -class VocabulariesGrid extends React.PureComponent { +class VocabularyTypesGrid extends React.PureComponent { render() { - logger.log(logger.DEBUG, 'VocabulariesGrid.render') + logger.log(logger.DEBUG, 'VocabularyTypesGrid.render') const { id, rows, selectedRowId, onSelectedRowChange, controllerRef } = this.props @@ -46,4 +46,4 @@ class VocabulariesGrid extends React.PureComponent { } } -export default VocabulariesGrid +export default VocabularyTypesGrid diff --git a/openbis_ng_ui/src/js/components/types/search/TypeSearch.jsx b/openbis_ng_ui/src/js/components/types/search/TypeSearch.jsx index 2e33fca2fa4..bf5ab935250 100644 --- a/openbis_ng_ui/src/js/components/types/search/TypeSearch.jsx +++ b/openbis_ng_ui/src/js/components/types/search/TypeSearch.jsx @@ -4,8 +4,8 @@ import React from 'react' import { withStyles } from '@material-ui/core/styles' import Container from '@src/js/components/common/form/Container.jsx' import GridContainer from '@src/js/components/common/grid/GridContainer.jsx' -import TypesGrid from '@src/js/components/types/common/TypesGrid.jsx' -import VocabulariesGrid from '@src/js/components/types/common/VocabulariesGrid.jsx' +import EntityTypesGrid from '@src/js/components/types/common/EntityTypesGrid.jsx' +import VocabularyTypesGrid from '@src/js/components/types/common/VocabularyTypesGrid.jsx' import PropertyTypesGrid from '@src/js/components/types/common/PropertyTypesGrid.jsx' import Message from '@src/js/components/common/form/Message.jsx' import ids from '@src/js/common/consts/ids.js' @@ -200,31 +200,28 @@ class TypeSearch extends React.Component { return } - const fo = new openbis.PropertyTypeFetchOptions() - fo.withVocabulary() - fo.withMaterialType() - fo.withSampleType() - - const result = await openbis.searchPropertyTypes( - new openbis.PropertyTypeSearchCriteria(), - fo - ) + const [propertyTypes, propertyTypeUsages] = await Promise.all([ + this.loadPropertyTypesTypes(), + this.loadPropertyTypesUsages() + ]) const types = util - .filter(result.objects, this.props.searchText, ['code', 'description']) + .filter(propertyTypes.objects, this.props.searchText, [ + 'code', + 'description' + ]) .map(object => ({ id: _.get(object, 'code'), code: _.get(object, 'code'), label: _.get(object, 'label'), description: _.get(object, 'description'), - internal: _.get(object, 'managedInternally'), dataType: _.get(object, 'dataType'), vocabulary: _.get(object, 'vocabulary.code'), materialType: _.get(object, 'materialType.code'), sampleType: _.get(object, 'sampleType.code'), schema: _.get(object, 'schema'), transformation: _.get(object, 'transformation'), - metaData: _.get(object, 'metaData') + usages: _.get(propertyTypeUsages, object.code) })) this.setState({ @@ -232,6 +229,93 @@ class TypeSearch extends React.Component { }) } + async loadPropertyTypesTypes() { + const fo = new openbis.PropertyTypeFetchOptions() + fo.withVocabulary() + fo.withMaterialType() + fo.withSampleType() + + const propertyTypes = await openbis.searchPropertyTypes( + new openbis.PropertyTypeSearchCriteria(), + fo + ) + + return propertyTypes + } + + async loadPropertyTypesUsages() { + const sampleTypeFo = new openbis.SampleTypeFetchOptions() + sampleTypeFo.withPropertyAssignments().withPropertyType() + + const experimentTypeFo = new openbis.ExperimentTypeFetchOptions() + experimentTypeFo.withPropertyAssignments().withPropertyType() + + const dataSetTypeFo = new openbis.DataSetTypeFetchOptions() + dataSetTypeFo.withPropertyAssignments().withPropertyType() + + const materialTypeFo = new openbis.MaterialTypeFetchOptions() + materialTypeFo.withPropertyAssignments().withPropertyType() + + const [sampleTypes, experimentTypes, dataSetTypes, materialTypes] = + await Promise.all([ + openbis.searchSampleTypes( + new openbis.SampleTypeSearchCriteria(), + sampleTypeFo + ), + openbis.searchExperimentTypes( + new openbis.ExperimentTypeSearchCriteria(), + experimentTypeFo + ), + openbis.searchDataSetTypes( + new openbis.DataSetTypeSearchCriteria(), + dataSetTypeFo + ), + openbis.searchMaterialTypes( + new openbis.MaterialTypeSearchCriteria(), + materialTypeFo + ) + ]) + + function addUsages(usages, entityTypes, entityKind) { + entityTypes.objects.forEach(entityType => { + entityType.propertyAssignments.forEach(propertyAssignment => { + let propertyUsages = usages[propertyAssignment.propertyType.code] + if (!propertyUsages) { + propertyUsages = { + sampleTypes: [], + experimentTypes: [], + dataSetTypes: [], + materialTypes: [] + } + usages[propertyAssignment.propertyType.code] = propertyUsages + } + propertyUsages[entityKind].push(entityType.code) + }) + }) + } + + const usages = {} + addUsages(usages, sampleTypes, 'sampleTypes') + addUsages(usages, experimentTypes, 'experimentTypes') + addUsages(usages, dataSetTypes, 'dataSetTypes') + addUsages(usages, materialTypes, 'materialTypes') + + Object.keys(usages).forEach(propertyTypeCode => { + const propertyUsages = usages[propertyTypeCode] + propertyUsages.sampleTypes.sort() + propertyUsages.experimentTypes.sort() + propertyUsages.dataSetTypes.sort() + propertyUsages.materialTypes.sort() + propertyUsages.count = + propertyUsages.sampleTypes.length + + propertyUsages.experimentTypes.length + + propertyUsages.dataSetTypes.length + + propertyUsages.materialTypes.length + }) + + return usages + } + shouldLoad(objectType) { return this.props.objectType === objectType || !this.props.objectType } @@ -312,7 +396,7 @@ class TypeSearch extends React.Component { const { classes } = this.props return ( <div className={classes.grid}> - <TypesGrid + <EntityTypesGrid id={ids.OBJECT_TYPES_GRID_ID} controllerRef={controller => (this.gridControllers[objectTypes.OBJECT_TYPE] = controller) @@ -337,7 +421,7 @@ class TypeSearch extends React.Component { const { classes } = this.props return ( <div className={classes.grid}> - <TypesGrid + <EntityTypesGrid id={ids.COLLECTION_TYPES_GRID_ID} controllerRef={controller => (this.gridControllers[objectTypes.COLLECTION_TYPE] = controller) @@ -360,7 +444,7 @@ class TypeSearch extends React.Component { const { classes } = this.props return ( <div className={classes.grid}> - <TypesGrid + <EntityTypesGrid id={ids.DATA_SET_TYPES_GRID_ID} controllerRef={controller => (this.gridControllers[objectTypes.DATA_SET_TYPE] = controller) @@ -385,7 +469,7 @@ class TypeSearch extends React.Component { const { classes } = this.props return ( <div className={classes.grid}> - <TypesGrid + <EntityTypesGrid id={ids.MATERIAL_TYPES_GRID_ID} controllerRef={controller => (this.gridControllers[objectTypes.MATERIAL_TYPE] = controller) @@ -410,7 +494,7 @@ class TypeSearch extends React.Component { const { classes } = this.props return ( <div className={classes.grid}> - <VocabulariesGrid + <VocabularyTypesGrid id={ids.VOCABULARY_TYPES_GRID_ID} controllerRef={controller => (this.gridControllers[objectTypes.VOCABULARY_TYPE] = controller) -- GitLab