From daf7a8f37e7e9d63c65ff347f16af51afc6d8c65 Mon Sep 17 00:00:00 2001 From: Lal Rishav <80503855+saxo-lalrishav@users.noreply.github.com> Date: Fri, 20 Aug 2021 00:55:14 +0530 Subject: [PATCH] feat(business-glossary): Business glossary relationship UI (#3129) --- .../mappers/GlossaryTermInfoMapper.java | 5 +- datahub-web-react/src/Mocks.tsx | 119 ++++++++++++++++++ .../profile/GlossaryRelatedTerms.tsx | 72 +++++++++++ .../profile/GlossaryRelatedTermsResult.tsx | 89 +++++++++++++ .../profile/GlossaryTermHeader.tsx | 2 +- .../profile/GlossaryTermProfile.tsx | 24 +++- .../glossaryTerm/profile/SchemaView.tsx | 30 +++++ .../__tests__/GlossaryRelatedTerms.test.tsx | 51 ++++++++ .../src/app/preview/DefaultPreviewCard.tsx | 2 +- .../src/app/shared/RoutedTabs.tsx | 1 + .../src/graphql/glossaryTerm.graphql | 5 +- 11 files changed, 390 insertions(+), 10 deletions(-) create mode 100644 datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedTerms.tsx create mode 100644 datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedTermsResult.tsx create mode 100644 datahub-web-react/src/app/entity/glossaryTerm/profile/SchemaView.tsx create mode 100644 datahub-web-react/src/app/entity/glossaryTerm/profile/__tests__/GlossaryRelatedTerms.test.tsx diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/glossary/mappers/GlossaryTermInfoMapper.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/glossary/mappers/GlossaryTermInfoMapper.java index 652bd8d5ca..76edfd5e00 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/glossary/mappers/GlossaryTermInfoMapper.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/glossary/mappers/GlossaryTermInfoMapper.java @@ -33,6 +33,9 @@ public class GlossaryTermInfoMapper implements ModelMapper { + if (menuOptionsArray && menuOptionsArray.length > 0 && selectedKey.length === 0) { + setSelectedKey(menuOptionsArray[0]); + } + }, [menuOptionsArray, selectedKey]); + + const onMenuClick = ({ key }) => { + setSelectedKey(key); + }; + + return ( + + + { + onMenuClick(key); + }} + > + {menuOptionsArray.map((option) => ( + + {RelatedTermTypes[option]} + + ))} + + + + {selectedKey && ( + + )} + + + ); +} diff --git a/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedTermsResult.tsx b/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedTermsResult.tsx new file mode 100644 index 0000000000..e787bbf5a0 --- /dev/null +++ b/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedTermsResult.tsx @@ -0,0 +1,89 @@ +import { QueryResult } from '@apollo/client'; +import { Divider, List, Typography } from 'antd'; +import React from 'react'; +import styled from 'styled-components'; +import { GetGlossaryTermQuery, useGetGlossaryTermQuery } from '../../../../graphql/glossaryTerm.generated'; +import { EntityType, Exact } from '../../../../types.generated'; +import { Message } from '../../../shared/Message'; +import { useEntityRegistry } from '../../../useEntityRegistry'; +import { PreviewType } from '../../Entity'; + +export type Props = { + glossaryRelatedTermType: string; + glossaryRelatedTermResult: Array; +}; + +const ListContainer = styled.div` + display: default; + flex-grow: default; +`; + +const TitleContainer = styled.div` + margin-bottom: 30px; +`; + +const ListItem = styled.div` + margin: 40px; + padding-bottom: 5px; +`; + +const Profile = styled.div` + marging-bottom: 20px; +`; + +const messageStyle = { marginTop: '10%' }; + +export default function GlossaryRelatedTermsResult({ glossaryRelatedTermType, glossaryRelatedTermResult }: Props) { + const entityRegistry = useEntityRegistry(); + const glossaryRelatedTermUrns: Array = []; + glossaryRelatedTermResult.forEach((item: any) => { + glossaryRelatedTermUrns.push(item?.entity?.urn); + }); + const glossaryTermInfo: QueryResult>[] = []; + + for (let i = 0; i < glossaryRelatedTermUrns.length; i++) { + glossaryTermInfo.push( + // eslint-disable-next-line react-hooks/rules-of-hooks + useGetGlossaryTermQuery({ + variables: { + urn: glossaryRelatedTermUrns[i], + }, + }), + ); + } + + const contentLoading = glossaryTermInfo.some((item) => { + return item.loading; + }); + return ( + <> + {contentLoading ? ( + + ) : ( + + + {glossaryRelatedTermType} + + + { + return ( + + + {entityRegistry.renderPreview( + EntityType.GlossaryTerm, + PreviewType.PREVIEW, + item?.data?.glossaryTerm, + )} + + + + ); + }} + /> + + )} + + ); +} diff --git a/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryTermHeader.tsx b/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryTermHeader.tsx index 1c44d5d553..75530f326d 100644 --- a/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryTermHeader.tsx +++ b/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryTermHeader.tsx @@ -15,7 +15,7 @@ export default function GlossaryTermHeader({ definition, sourceRef, sourceUrl, o const entityRegistry = useEntityRegistry(); return ( <> - + {definition} }> Source diff --git a/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryTermProfile.tsx b/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryTermProfile.tsx index adcf26e84a..420aad77df 100644 --- a/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryTermProfile.tsx +++ b/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryTermProfile.tsx @@ -1,6 +1,6 @@ import { Alert } from 'antd'; import React, { useMemo } from 'react'; -import { useGetGlossaryTermQuery } from '../../../../graphql/glossaryTerm.generated'; +import { GetGlossaryTermQuery, useGetGlossaryTermQuery } from '../../../../graphql/glossaryTerm.generated'; import { EntityType, GlossaryTerm, SearchResult } from '../../../../types.generated'; import { useGetEntitySearchResults } from '../../../../utils/customGraphQL/useGetEntitySearchResults'; import { EntityProfile } from '../../../shared/EntityProfile'; @@ -9,16 +9,20 @@ import useUserParams from '../../../shared/entitySearch/routingUtils/useUserPara import { Message } from '../../../shared/Message'; import { useEntityRegistry } from '../../../useEntityRegistry'; import { Properties as PropertiesView } from '../../shared/Properties'; +import GlossayRelatedTerms from './GlossaryRelatedTerms'; import GlossaryTermHeader from './GlossaryTermHeader'; +import SchemaView from './SchemaView'; const messageStyle = { marginTop: '10%' }; export enum TabType { RelatedEntity = 'Related Entities', + RelatedGlossaryTerms = 'Related Terms', + Schema = 'Schema', Properties = 'Properties', } -const ENABLED_TAB_TYPES = [TabType.Properties, TabType.RelatedEntity]; +const ENABLED_TAB_TYPES = [TabType.Properties, TabType.RelatedEntity, TabType.RelatedGlossaryTerms, TabType.Schema]; export default function GlossaryTermProfile() { const { urn } = useUserParams(); @@ -56,17 +60,27 @@ export default function GlossaryTermProfile() { return filteredSearchResult; }, [entitySearchResult]); - const getTabs = ({ glossaryTermInfo }: GlossaryTerm) => { + const getTabs = ({ glossaryTerm }: GetGlossaryTermQuery) => { return [ { name: TabType.RelatedEntity, path: TabType.RelatedEntity.toLocaleLowerCase(), content: , }, + { + name: TabType.RelatedGlossaryTerms, + path: TabType.RelatedGlossaryTerms.toLocaleLowerCase(), + content: , + }, + { + name: TabType.Schema, + path: TabType.Schema.toLocaleLowerCase(), + content: , + }, { name: TabType.Properties, path: TabType.Properties.toLocaleLowerCase(), - content: , + content: , }, ].filter((tab) => ENABLED_TAB_TYPES.includes(tab.name)); }; @@ -94,7 +108,7 @@ export default function GlossaryTermProfile() { title={data.glossaryTerm.name} tags={null} header={getHeader(data?.glossaryTerm as GlossaryTerm)} - tabs={getTabs(data.glossaryTerm as GlossaryTerm)} + tabs={getTabs(data)} /> )} diff --git a/datahub-web-react/src/app/entity/glossaryTerm/profile/SchemaView.tsx b/datahub-web-react/src/app/entity/glossaryTerm/profile/SchemaView.tsx new file mode 100644 index 0000000000..46484459ba --- /dev/null +++ b/datahub-web-react/src/app/entity/glossaryTerm/profile/SchemaView.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Empty, Typography } from 'antd'; +import styled from 'styled-components'; + +export type Props = { + rawSchema: string | null; +}; + +const Content = styled.div` + margin-left: 32px; + flex-grow: 1; +`; + +export default function SchemaView({ rawSchema }: Props) { + return ( + <> + {rawSchema && rawSchema.length > 0 ? ( + +
+                        {rawSchema}
+                    
+
+ ) : ( + + + + )} + + ); +} diff --git a/datahub-web-react/src/app/entity/glossaryTerm/profile/__tests__/GlossaryRelatedTerms.test.tsx b/datahub-web-react/src/app/entity/glossaryTerm/profile/__tests__/GlossaryRelatedTerms.test.tsx new file mode 100644 index 0000000000..8bc57b7be8 --- /dev/null +++ b/datahub-web-react/src/app/entity/glossaryTerm/profile/__tests__/GlossaryRelatedTerms.test.tsx @@ -0,0 +1,51 @@ +import { render } from '@testing-library/react'; +import React from 'react'; +import { MockedProvider } from '@apollo/client/testing'; +import TestPageContainer from '../../../../../utils/test-utils/TestPageContainer'; +import GlossaryRelatedTerms from '../GlossaryRelatedTerms'; +import { mocks } from '../../../../../Mocks'; + +const glossaryRelatedTermData = { + isRealtedTerms: { + start: 0, + count: 0, + total: 0, + relationships: [ + { + entity: { + urn: 'urn:li:glossaryTerm:schema.Field16Schema_v1', + __typename: 'GlossaryTerm', + }, + }, + ], + __typename: 'EntityRelationshipsResult', + }, + hasRelatedTerms: { + start: 0, + count: 0, + total: 0, + relationships: [ + { + entity: { + urn: 'urn:li:glossaryTerm:example.glossaryterm2', + __typename: 'GlossaryTerm', + }, + }, + ], + __typename: 'EntityRelationshipsResult', + }, +}; + +describe('Glossary Related Terms', () => { + it('renders and print hasRelatedTerms detail by default', async () => { + const { getByText } = render( + + + + + , + ); + expect(getByText('Composed Of')).toBeInTheDocument(); + expect(getByText('Defined in')).toBeInTheDocument(); + }); +}); diff --git a/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx b/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx index 81f8fea1cc..533df855e0 100644 --- a/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx +++ b/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx @@ -45,7 +45,7 @@ const PreviewImage = styled(Image)` `; const styles = { - row: { width: '100%', marginBottom: '0px' }, + row: { width: '100%', marginBottom: '20px' }, leftColumn: { maxWidth: '75%' }, rightColumn: { maxWidth: '25%' }, name: { fontSize: '18px' }, diff --git a/datahub-web-react/src/app/shared/RoutedTabs.tsx b/datahub-web-react/src/app/shared/RoutedTabs.tsx index 7582d3b7e1..8e4d508ede 100644 --- a/datahub-web-react/src/app/shared/RoutedTabs.tsx +++ b/datahub-web-react/src/app/shared/RoutedTabs.tsx @@ -35,6 +35,7 @@ export const RoutedTabs = ({ defaultPath, tabs, onTabChange, ...props }: Props)
onTabChange && onTabChange(tab)} onChange={(newPath) => history.push(`${url}/${newPath}`)} diff --git a/datahub-web-react/src/graphql/glossaryTerm.graphql b/datahub-web-react/src/graphql/glossaryTerm.graphql index a58ebdb257..911681974d 100644 --- a/datahub-web-react/src/graphql/glossaryTerm.graphql +++ b/datahub-web-react/src/graphql/glossaryTerm.graphql @@ -4,7 +4,7 @@ query getGlossaryTerm($urn: String!, $start: Int, $count: Int) { type name hierarchicalName - isRealtedTerms: relationships(types: ["IsA"], direction: OUTGOING, start: $start, count: $count) { + isRelatedTerms: relationships(types: ["IsA"], direction: OUTGOING, start: $start, count: $count) { start count total @@ -16,7 +16,7 @@ query getGlossaryTerm($urn: String!, $start: Int, $count: Int) { } } } - hasRealtedTerms: relationships(types: ["HasA"], direction: OUTGOING, start: $start, count: $count) { + hasRelatedTerms: relationships(types: ["HasA"], direction: OUTGOING, start: $start, count: $count) { start count total @@ -36,6 +36,7 @@ query getGlossaryTerm($urn: String!, $start: Int, $count: Int) { termSource sourceRef sourceUrl + rawSchema customProperties { key value