From aeab5cc6bdf413f0d0d26af80ab3afd885fd357f Mon Sep 17 00:00:00 2001 From: Sny Date: Thu, 28 Nov 2024 16:10:03 +0530 Subject: [PATCH] OpenConceptLab/ocl_issues#1972 | Name space associations --- src/components/concepts/Associations.jsx | 282 ++++++++++++++------- src/components/concepts/ConceptDetails.jsx | 4 +- src/components/concepts/ConceptHome.jsx | 62 ++++- src/i18n/locales/en/translations.json | 1 + 4 files changed, 254 insertions(+), 95 deletions(-) diff --git a/src/components/concepts/Associations.jsx b/src/components/concepts/Associations.jsx index ae73f261..01a2ee98 100644 --- a/src/components/concepts/Associations.jsx +++ b/src/components/concepts/Associations.jsx @@ -9,11 +9,14 @@ import TableBody from '@mui/material/TableBody' import TableRow from '@mui/material/TableRow' import TableCell from '@mui/material/TableCell' import Tooltip from '@mui/material/Tooltip' +import Button from '@mui/material/Button' import Chip from '@mui/material/Chip' -import { get, isEmpty, forEach, map, find, compact, flatten, values } from 'lodash'; +import Skeleton from '@mui/material/Skeleton' +import { get, isEmpty, forEach, map, find, compact, flatten, values, filter } from 'lodash'; import ConceptIcon from './ConceptIcon' -import { generateRandomString, dropVersion } from '../../common/utils' +import { generateRandomString, dropVersion, URIToParentParams, toParentURI } from '../../common/utils' import TagCountLabel from '../common/TagCountLabel' +import RepoChip from '../repos/RepoChip' const groupMappings = (orderedMappings, concept, mappings, forward) => { forEach(mappings, resource => { @@ -23,7 +26,7 @@ const groupMappings = (orderedMappings, concept, mappings, forward) => { if(!mapType) mapType = forward ? 'children' : 'parent'; orderedMappings[mapType] = orderedMappings[mapType] || {order: null, direct: [], indirect: [], unknown: [], hierarchy: [], reverseHierarchy: [], self: []} - const isSelfMapping = isMapping && dropVersion(concept.url) === dropVersion(resource.cascade_target_concept_url) + const isSelfMapping = isMapping && dropVersion(concept.url) === dropVersion(resource.cascade_target_concept_url) && toParentURI(concept.url) === dropVersion(resource.cascade_target_concept_url) let _resource = isMapping ? {...resource, isSelf: isSelfMapping, cascade_target_concept_name: resource.cascade_target_concept_name || get(find(mappings, m => dropVersion(m.url) === dropVersion(resource.cascade_target_concept_url)), 'display_name')} : {...resource, cascade_target_concept_name: resource.display_name} if(isSelfMapping) { if(!map(orderedMappings[mapType].self, 'id').includes(resource.id)) @@ -123,8 +126,10 @@ const AssociationRow = ({mappings, id, mapType, isSelf, isIndirect}) => { const borderColor = 'rgba(0, 0, 0, 0.12)' -const Associations = ({concept, mappings, reverseMappings}) => { +const Associations = ({concept, mappings, reverseMappings, ownerMappings, reverseOwnerMappings, onLoadOwnerMappings, loadingOwnerMappings}) => { const [orderedMappings, setOrderedMappings] = React.useState({}); + const [orderedOwnerMappings, setOrderedOwnerMappings] = React.useState({}); + const [ownerMappingsGroupedByRepo, setOwnerMappingsGroupedByRepo] = React.useState({}); const { t } = useTranslation() const getMappings = () => { let _mappings = {} @@ -132,104 +137,203 @@ const Associations = ({concept, mappings, reverseMappings}) => { groupMappings(_mappings, concept, reverseMappings, false) return _mappings } + const getOwnerMappings = () => { + let _ownerMappingsGroupedByRepo = {} + let groupedMappings = {} + forEach(ownerMappings, _mapping => { + let url = _mapping?.version_url || _mapping?.url + let parent = URIToParentParams(url) + let parentURI = toParentURI(url) + _mapping.direct = true + _mapping.parent = parent + _ownerMappingsGroupedByRepo[parentURI] ||= [] + _ownerMappingsGroupedByRepo[parentURI].push(_mapping) + }) + forEach(reverseOwnerMappings, _mapping => { + _mapping.indirect = true + let url = _mapping?.version_url || _mapping?.url + let parent = URIToParentParams(url) + let parentURI = toParentURI(url) + _mapping.parent = parent + _ownerMappingsGroupedByRepo[parentURI] ||= [] + _ownerMappingsGroupedByRepo[parentURI].push(_mapping) + }) + setOwnerMappingsGroupedByRepo(_ownerMappingsGroupedByRepo) + forEach(_ownerMappingsGroupedByRepo, (mappings, repoURI) => { + let __mappings = {} + groupMappings(__mappings, concept, filter(mappings, {direct: true}), true) + groupMappings(__mappings, concept, filter(mappings, {indirect: true}), false) + groupedMappings[repoURI] = __mappings + }) + return groupedMappings + } - const count = flatten(compact(flatten(map(values(orderedMappings), mapping => values(mapping))))).length + const countOwnerMappings = ownerMappings?.length + reverseOwnerMappings?.length + const count = flatten(compact(flatten(map(values(orderedMappings), mapping => values(mapping))))).length + countOwnerMappings React.useEffect(() => setOrderedMappings(getMappings()), [mappings, reverseMappings]) + React.useEffect(() => setOrderedOwnerMappings(getOwnerMappings()), [ownerMappings, reverseOwnerMappings]) return ( - 0 ? '1px solid' : '0', borderColor: borderColor, padding: '12px 16px', fontSize: '16px', color: 'surface.contrastText', display: 'flex', justifyContent: 'space-between'}}> + - { - count > 0 && - - - - - {t('mapping.relationship')} - {t('mapping.code')} - {t('common.name')} - {t('repo.source')} - - - - { - map(orderedMappings, (oMappings, mapType) => { - const key = generateRandomString() - const hasSelfMappings = !isEmpty(oMappings.self) - return hasSelfMappings && - + +
+ + + {t('mapping.relationship')} + {t('mapping.code')} + {t('common.name')} + {t('repo.source')} + + + + { + map(orderedMappings, (oMappings, mapType) => { + const key = generateRandomString() + const hasSelfMappings = !isEmpty(oMappings.self) + return hasSelfMappings && + + + + }) + } + { + !isEmpty(orderedMappings?.children?.hierarchy) && + + } + { + !isEmpty(orderedMappings?.parent?.reverseHierarchy) && + + } + { + map(orderedMappings, (oMappings, mapType) => { + const key = generateRandomString() + const hasDirectMappings = !isEmpty(oMappings.direct) + return ( + + { + hasDirectMappings && - - }) - } - { - !isEmpty(orderedMappings?.children?.hierarchy) && - - } - { - !isEmpty(orderedMappings?.parent?.reverseHierarchy) && - - } - { - map(orderedMappings, (oMappings, mapType) => { - const key = generateRandomString() - const hasDirectMappings = !isEmpty(oMappings.direct) - return ( - - { - hasDirectMappings && - - } - - ) - }) - } + } + + ) + }) + } + { + map(orderedMappings, (oMappings, mapType) => { + const key = generateRandomString() + const hasMappings = !isEmpty(oMappings.indirect) + return ( + + { + hasMappings && + + } + + ) + }) + } + + + { loadingOwnerMappings === true && } + { loadingOwnerMappings === false && `${t('concept.namespace_associations')} (${countOwnerMappings})` } { - map(orderedMappings, (oMappings, mapType) => { - const key = generateRandomString() - const hasMappings = !isEmpty(oMappings.indirect) - return ( - - { - hasMappings && - - } - - ) - }) + loadingOwnerMappings === null + && + } - -
-
- } + + + { + map(orderedOwnerMappings, (gMappings, repoURI) => { + const repoMappings = ownerMappingsGroupedByRepo[repoURI] + const repo = repoMappings[0].parent + return ( + + + + + } + count={repoMappings?.length} + /> + + + { + map(gMappings, (oMappings, mapType) => { + const key = generateRandomString() + const hasMappings = !isEmpty(oMappings.direct) + return ( + + { + hasMappings && + + } + + ) + }) + } + { + map(gMappings, (oMappings, mapType) => { + const key = generateRandomString() + const hasMappings = !isEmpty(oMappings.indirect) + return ( + + { + hasMappings && + + } + + ) + }) + } + + ) + }) + } + + +
) } diff --git a/src/components/concepts/ConceptDetails.jsx b/src/components/concepts/ConceptDetails.jsx index 862482cc..bb3fb7bb 100644 --- a/src/components/concepts/ConceptDetails.jsx +++ b/src/components/concepts/ConceptDetails.jsx @@ -11,7 +11,7 @@ import ConceptProperties from './ConceptProperties' const borderColor = 'rgba(0, 0, 0, 0.12)' -const ConceptDetails = ({ concept, repo, mappings, reverseMappings, loading }) => { +const ConceptDetails = ({ concept, repo, mappings, reverseMappings, loading, loadingOwnerMappings, ownerMappings, reverseOwnerMappings, onLoadOwnerMappings }) => { const { t } = useTranslation() const updatedBy = concept?.version_updated_by || concept?.updated_by return ( @@ -47,7 +47,7 @@ const ConceptDetails = ({ concept, repo, mappings, reverseMappings, loading }) = { loading ? : - + } diff --git a/src/components/concepts/ConceptHome.jsx b/src/components/concepts/ConceptHome.jsx index b99b99f8..878b82bf 100644 --- a/src/components/concepts/ConceptHome.jsx +++ b/src/components/concepts/ConceptHome.jsx @@ -13,14 +13,19 @@ const ConceptHome = props => { const location = useLocation() const isInitialMount = React.useRef(true); - const [loading, setLoading] = React.useState(false) const [concept, setConcept] = React.useState(props.concept || {}) - const [mappings, setMappings] = React.useState([]) - const [reverseMappings, setReverseMappings] = React.useState([]) + const [repo, setRepo] = React.useState(props.repo || {}) const [tab, setTab] = React.useState('metadata') const [edit, setEdit] = React.useState(false) + const [loading, setLoading] = React.useState(false) + const [loadingOwnerMappings, setLoadingOwnerMappings] = React.useState(null) + const [mappings, setMappings] = React.useState([]) + const [reverseMappings, setReverseMappings] = React.useState([]) + const [ownerMappings, setOwnerMappings] = React.useState([]) + const [reverseOwnerMappings, setReverseOwnerMappings] = React.useState([]) + React.useEffect(() => { setLoading(true) setConcept(props.concept || {}) @@ -104,6 +109,45 @@ const ConceptHome = props => { }) } + const getOwnerMappings = (concept, directOnly) => { + setLoadingOwnerMappings(true) + APIService + .mappings() + .get(null, null, { + ownerType: concept.owner_type, + owner: concept.owner, + fromConcept: concept.id, + fromConceptSource: concept.source, + source: `!${concept.source}`, + brief: true, + pageSize: 1000 + }) + .then(response => { + setOwnerMappings(response?.data || []) + if(directOnly) + setTimeout(() => setLoadingOwnerMappings(false), 300) + !directOnly && getInverseOwnerMappings(concept) + }) + } + + const getInverseOwnerMappings = concept => { + APIService + .mappings() + .get(null, null, { + ownerType: concept.owner_type, + owner: concept.owner, + toConcept: concept.id, + toConceptSource: concept.source, + source: `!${concept.source}`, + brief: true, + pageSize: 1000 + }) + .then(response => { + setReverseOwnerMappings(response?.data || []) + setTimeout(() => setLoadingOwnerMappings(false), 300) + }) + } + return (concept?.id && repo?.id) ? ( <> @@ -132,7 +176,17 @@ const ConceptHome = props => { setTab(newTab)} loading={loading} /> { tab === 'metadata' && - + getOwnerMappings(concept)} + /> } } diff --git a/src/i18n/locales/en/translations.json b/src/i18n/locales/en/translations.json index 96de5e20..f5b4ca4b 100644 --- a/src/i18n/locales/en/translations.json +++ b/src/i18n/locales/en/translations.json @@ -138,6 +138,7 @@ "copied_name": "Concept name URL copied to clipboard", "copied_description": "Concept description URL copied to clipboard", "associations": "Associations", + "namespace_associations": "Namespace Associations", "concept_classes": "Concept Classes", "name_type": "Name Type", "name_types": "Name Types",