feat(ui): Selector recommendations in Owner, Tag and Domain Modal (#5197)

This commit is contained in:
Ankit keshari 2022-06-28 22:15:25 +05:30 committed by GitHub
parent b76005d640
commit bf7da0a853
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 399 additions and 285 deletions

View File

@ -50,16 +50,17 @@ export default function GroupOwnerSideBarSection({ urn, ownership, refetch }: Pr
</AddOwnerButton>
)}
</SectionWrapper>
<AddOwnersModal
urn={urn}
hideOwnerType
type={EntityType.CorpGroup}
visible={showAddModal}
refetch={refetch}
onCloseModal={() => {
setShowAddModal(false);
}}
/>
{showAddModal && (
<AddOwnersModal
urn={urn}
hideOwnerType
type={EntityType.CorpGroup}
refetch={refetch}
onCloseModal={() => {
setShowAddModal(false);
}}
/>
)}
</>
);
}

View File

@ -1,53 +1,104 @@
import { Button, Form, message, Modal, Select, Tag } from 'antd';
import React, { useRef, useState } from 'react';
import { Button, Form, message, Modal, Select, Tag } from 'antd';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { useGetSearchResultsLazyQuery } from '../../../../../../../graphql/search.generated';
import { EntityType, SearchResult } from '../../../../../../../types.generated';
import { Entity, EntityType } from '../../../../../../../types.generated';
import { useSetDomainMutation } from '../../../../../../../graphql/mutations.generated';
import { useEntityRegistry } from '../../../../../../useEntityRegistry';
import { useEntityData } from '../../../../EntityContext';
import { useEnterKeyListener } from '../../../../../../shared/useEnterKeyListener';
import { useGetRecommendations } from '../../../../../../shared/recommendation';
import { DomainLabel } from '../../../../../../shared/DomainLabel';
type Props = {
visible: boolean;
onClose: () => void;
onCloseModal: () => void;
refetch?: () => Promise<any>;
};
const SearchResultContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
`;
const SearchResultContent = styled.div`
display: flex;
justify-content: start;
align-items: center;
`;
const SearchResultDisplayName = styled.div`
margin-left: 12px;
`;
type SelectedDomain = {
displayName: string;
type: EntityType;
urn: string;
};
export const SetDomainModal = ({ visible, onClose, refetch }: Props) => {
const StyleTag = styled(Tag)`
padding: 0px 7px;
margin-right: 3px;
display: flex;
justify-content: start;
align-items: center;
`;
export const SetDomainModal = ({ onCloseModal, refetch }: Props) => {
const entityRegistry = useEntityRegistry();
const { urn } = useEntityData();
const [inputValue, setInputValue] = useState('');
const [selectedDomain, setSelectedDomain] = useState<SelectedDomain | undefined>(undefined);
const [domainSearch, { data: domainSearchData }] = useGetSearchResultsLazyQuery();
const domainSearchResults = domainSearchData?.search?.searchResults || [];
const domainSearchResults =
domainSearchData?.search?.searchResults?.map((searchResult) => searchResult.entity) || [];
const [setDomainMutation] = useSetDomainMutation();
const [recommendedData] = useGetRecommendations([EntityType.Domain]);
const inputEl = useRef(null);
const onModalClose = () => {
setInputValue('');
setSelectedDomain(undefined);
onCloseModal();
};
const handleSearch = (text: string) => {
if (text.length > 2) {
domainSearch({
variables: {
input: {
type: EntityType.Domain,
query: text,
start: 0,
count: 5,
},
},
});
}
};
// Renders a search result in the select dropdown.
const renderSearchResult = (entity: Entity) => {
const displayName = entityRegistry.getDisplayName(entity.type, entity);
return (
<Select.Option value={entity.urn} key={entity.urn}>
<DomainLabel name={displayName} />
</Select.Option>
);
};
const domainResult = !inputValue || inputValue.length === 0 ? recommendedData : domainSearchResults;
const domainSearchOptions = domainResult?.map((result) => {
return renderSearchResult(result);
});
const onSelectDomain = (newUrn: string) => {
if (inputEl && inputEl.current) {
(inputEl.current as any).blur();
}
const filteredDomains = domainResult?.filter((entity) => entity.urn === newUrn).map((entity) => entity) || [];
if (filteredDomains.length) {
const domain = filteredDomains[0];
setSelectedDomain({
displayName: entityRegistry.getDisplayName(EntityType.Domain, domain),
type: EntityType.Domain,
urn: newUrn,
});
}
};
const onDeselectDomain = () => {
setInputValue('');
setSelectedDomain(undefined);
};
const onOk = async () => {
if (!selectedDomain) {
return;
@ -68,75 +119,42 @@ export const SetDomainModal = ({ visible, onClose, refetch }: Props) => {
}
setSelectedDomain(undefined);
refetch?.();
onClose();
onModalClose();
};
const onSelectDomain = (newUrn: string) => {
if (inputEl && inputEl.current) {
(inputEl.current as any).blur();
}
const filteredDomains =
domainSearchResults?.filter((result) => result.entity.urn === newUrn).map((result) => result.entity) || [];
if (filteredDomains.length) {
const domain = filteredDomains[0];
setSelectedDomain({
displayName: entityRegistry.getDisplayName(EntityType.Domain, domain),
type: EntityType.Domain,
urn: newUrn,
});
}
};
const handleSearch = (text: string) => {
if (text.length > 2) {
domainSearch({
variables: {
input: {
type: EntityType.Domain,
query: text,
start: 0,
count: 5,
},
},
});
}
};
const selectValue = (selectedDomain && [selectedDomain?.displayName]) || undefined;
// Handle the Enter press
useEnterKeyListener({
querySelectorToExecuteClick: '#setDomainButton',
});
const renderSearchResult = (result: SearchResult) => {
const displayName = entityRegistry.getDisplayName(result.entity.type, result.entity);
const tagRender = (props) => {
// eslint-disable-next-line react/prop-types
const { label, closable, onClose } = props;
const onPreventMouseDown = (event) => {
event.preventDefault();
event.stopPropagation();
};
return (
<SearchResultContainer>
<SearchResultContent>
<SearchResultDisplayName>
<div>{displayName}</div>
</SearchResultDisplayName>
</SearchResultContent>
<Link
target="_blank"
rel="noopener noreferrer"
to={() => `/${entityRegistry.getPathName(result.entity.type)}/${result.entity.urn}`}
>
View
</Link>{' '}
</SearchResultContainer>
<StyleTag onMouseDown={onPreventMouseDown} closable={closable} onClose={onClose}>
{label}
</StyleTag>
);
};
const selectValue = (selectedDomain && [selectedDomain?.displayName]) || [];
function handleBlur() {
setInputValue('');
}
return (
<Modal
title="Set Domain"
visible={visible}
onCancel={onClose}
visible
onCancel={onModalClose}
footer={
<>
<Button onClick={onClose} type="text">
<Button onClick={onModalClose} type="text">
Cancel
</Button>
<Button id="setDomainButton" disabled={selectedDomain === undefined} onClick={onOk}>
@ -149,21 +167,26 @@ export const SetDomainModal = ({ visible, onClose, refetch }: Props) => {
<Form.Item>
<Select
autoFocus
value={selectValue}
defaultOpen
filterOption={false}
showSearch
mode="multiple"
ref={inputEl}
defaultActiveFirstOption={false}
placeholder="Search for Domains..."
onSelect={(domainUrn: any) => onSelectDomain(domainUrn)}
onDeselect={() => setSelectedDomain(undefined)}
onSearch={handleSearch}
filterOption={false}
tagRender={(tagProps) => <Tag>{tagProps.value}</Tag>}
onDeselect={onDeselectDomain}
onSearch={(value: string) => {
// eslint-disable-next-line react/prop-types
handleSearch(value.trim());
// eslint-disable-next-line react/prop-types
setInputValue(value.trim());
}}
ref={inputEl}
value={selectValue}
tagRender={tagRender}
onBlur={handleBlur}
>
{domainSearchResults.map((result) => {
return (
<Select.Option value={result.entity.urn}>{renderSearchResult(result)}</Select.Option>
);
})}
{domainSearchOptions}
</Select>
</Form.Item>
</Form>

View File

@ -73,13 +73,14 @@ export const SidebarDomainSection = () => {
</>
)}
</div>
<SetDomainModal
visible={showModal}
refetch={refetch}
onClose={() => {
setShowModal(false);
}}
/>
{showModal && (
<SetDomainModal
refetch={refetch}
onCloseModal={() => {
setShowModal(false);
}}
/>
)}
</div>
);
};

View File

@ -1,33 +1,15 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { Button, Form, message, Modal, Select, Tag, Typography } from 'antd';
import styled from 'styled-components';
import {
CorpUser,
EntityType,
OwnerEntityType,
OwnershipType,
SearchResult,
} from '../../../../../../../types.generated';
import { CorpUser, Entity, EntityType, OwnerEntityType, OwnershipType } from '../../../../../../../types.generated';
import { useEntityRegistry } from '../../../../../../useEntityRegistry';
import { CustomAvatar } from '../../../../../../shared/avatar';
import analytics, { EventType, EntityActionType } from '../../../../../../analytics';
import { OWNERSHIP_DISPLAY_TYPES } from './ownershipUtils';
import { useAddOwnersMutation } from '../../../../../../../graphql/mutations.generated';
import { useGetSearchResultsLazyQuery } from '../../../../../../../graphql/search.generated';
const SearchResultContainer = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 2px;
`;
const SearchResultContent = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
import { useGetRecommendations } from '../../../../../../shared/recommendation';
import { OwnerLabel } from '../../../../../../shared/OwnerLabel';
const SelectInput = styled(Select)`
> .ant-select-selector {
@ -35,10 +17,17 @@ const SelectInput = styled(Select)`
}
`;
const StyleTag = styled(Tag)`
padding: 0px 7px 0px 0px;
margin-right: 3px;
display: flex;
justify-content: start;
align-items: center;
`;
type Props = {
urn: string;
type: EntityType;
visible: boolean;
defaultOwnerType?: OwnershipType;
hideOwnerType?: boolean | undefined;
onCloseModal: () => void;
@ -51,16 +40,9 @@ type SelectedOwner = {
value;
};
export const AddOwnersModal = ({
urn,
type,
visible,
hideOwnerType,
defaultOwnerType,
onCloseModal,
refetch,
}: Props) => {
export const AddOwnersModal = ({ urn, type, hideOwnerType, defaultOwnerType, onCloseModal, refetch }: Props) => {
const entityRegistry = useEntityRegistry();
const [inputValue, setInputValue] = useState('');
const [addOwnersMutation] = useAddOwnersMutation();
const ownershipTypes = OWNERSHIP_DISPLAY_TYPES;
const [selectedOwners, setSelectedOwners] = useState<SelectedOwner[]>([]);
@ -69,12 +51,11 @@ export const AddOwnersModal = ({
// User and group dropdown search results!
const [userSearch, { data: userSearchData }] = useGetSearchResultsLazyQuery();
const [groupSearch, { data: groupSearchData }] = useGetSearchResultsLazyQuery();
const userSearchResults = userSearchData?.search?.searchResults || [];
const groupSearchResults = groupSearchData?.search?.searchResults || [];
const userSearchResults = userSearchData?.search?.searchResults?.map((searchResult) => searchResult.entity) || [];
const groupSearchResults = groupSearchData?.search?.searchResults?.map((searchResult) => searchResult.entity) || [];
const combinedSearchResults = [...userSearchResults, ...groupSearchResults];
// Add owners Form
const [form] = Form.useForm();
const [recommendedData] = useGetRecommendations([EntityType.CorpGroup, EntityType.CorpUser]);
const inputEl = useRef(null);
useEffect(() => {
if (ownershipTypes) {
@ -82,43 +63,6 @@ export const AddOwnersModal = ({
}
}, [ownershipTypes]);
/**
* When a owner search result is selected, add the new owner to the selectedOwners
* value: {ownerUrn: string, ownerEntityType: EntityType}
*/
const onSelectOwner = (selectedValue: { key: string; label: React.ReactNode; value: string }) => {
const filteredActors = combinedSearchResults
.filter((result) => result.entity.urn === selectedValue.value)
.map((result) => result.entity);
if (filteredActors.length) {
const actor = filteredActors[0];
const ownerEntityType =
actor && actor.type === EntityType.CorpGroup ? OwnerEntityType.CorpGroup : OwnerEntityType.CorpUser;
const newValues = [
...selectedOwners,
{
label: selectedValue.value,
value: {
ownerUrn: selectedValue.value,
ownerEntityType,
},
},
];
setSelectedOwners(newValues);
}
};
// When a owner search result is deselected, remove the Owner
const onDeselectOwner = (selectedValue: { key: string; label: React.ReactNode; value: string }) => {
const newValues = selectedOwners.filter((owner) => owner.label !== selectedValue.value);
setSelectedOwners(newValues);
};
// When a owner type is selected, set the type as selected type.
const onSelectOwnerType = (newType: OwnershipType) => {
setSelectedOwnerType(newType);
};
// Invokes the search API as the owner types
const handleSearch = (entityType: EntityType, text: string, searchQuery: any) => {
if (text.length > 2) {
@ -142,34 +86,87 @@ export const AddOwnersModal = ({
};
// Renders a search result in the select dropdown.
const renderSearchResult = (result: SearchResult) => {
const renderSearchResult = (entity: Entity) => {
const avatarUrl =
result.entity.type === EntityType.CorpUser
? (result.entity as CorpUser).editableProperties?.pictureLink || undefined
entity.type === EntityType.CorpUser
? (entity as CorpUser).editableProperties?.pictureLink || undefined
: undefined;
const displayName = entityRegistry.getDisplayName(result.entity.type, result.entity);
const displayName = entityRegistry.getDisplayName(entity.type, entity);
return (
<SearchResultContainer>
<SearchResultContent>
<CustomAvatar
size={24}
name={displayName}
photoUrl={avatarUrl}
isGroup={result.entity.type === EntityType.CorpGroup}
/>
<div>{displayName}</div>
</SearchResultContent>
</SearchResultContainer>
<Select.Option value={entity.urn} key={entity.urn}>
<OwnerLabel name={displayName} avatarUrl={avatarUrl} type={entity.type} />
</Select.Option>
);
};
const ownerResult = !inputValue || inputValue.length === 0 ? recommendedData : combinedSearchResults;
const ownerSearchOptions = ownerResult?.map((result) => {
return renderSearchResult(result);
});
const onModalClose = () => {
setInputValue('');
setSelectedOwners([]);
setSelectedOwnerType(defaultOwnerType || OwnershipType.None);
form.resetFields();
onCloseModal();
};
/**
* When a owner search result is selected, add the new owner to the selectedOwners
* value: {ownerUrn: string, ownerEntityType: EntityType}
*/
const onSelectOwner = (selectedValue: { key: string; label: React.ReactNode; value: string }) => {
if (inputEl && inputEl.current) {
(inputEl.current as any).blur();
}
const filteredActors = ownerResult
?.filter((entity) => entity.urn === selectedValue.value)
.map((entity) => entity);
if (filteredActors?.length) {
const actor = filteredActors[0];
const ownerEntityType =
actor && actor.type === EntityType.CorpGroup ? OwnerEntityType.CorpGroup : OwnerEntityType.CorpUser;
const newValues = [
...selectedOwners,
{
label: selectedValue.value,
value: {
ownerUrn: selectedValue.value,
ownerEntityType,
},
},
];
setSelectedOwners(newValues);
}
};
// When a owner search result is deselected, remove the Owner
const onDeselectOwner = (selectedValue: { key: string; label: React.ReactNode; value: string }) => {
setInputValue('');
const newValues = selectedOwners.filter((owner) => owner.label !== selectedValue.value);
setSelectedOwners(newValues);
};
// When a owner type is selected, set the type as selected type.
const onSelectOwnerType = (newType: OwnershipType) => {
setSelectedOwnerType(newType);
};
const tagRender = (props) => {
// eslint-disable-next-line react/prop-types
const { label, closable, onClose } = props;
const onPreventMouseDown = (event) => {
event.preventDefault();
event.stopPropagation();
};
return (
<StyleTag onMouseDown={onPreventMouseDown} closable={closable} onClose={onClose}>
{label}
</StyleTag>
);
};
// Function to handle the modal action's
const onOk = async () => {
if (selectedOwners.length === 0) {
@ -210,35 +207,14 @@ export const AddOwnersModal = ({
}
};
const tagRender = (props) => {
// eslint-disable-next-line react/prop-types
const { label, closable, onClose } = props;
const onPreventMouseDown = (event) => {
event.preventDefault();
event.stopPropagation();
};
return (
<Tag
onMouseDown={onPreventMouseDown}
closable={closable}
onClose={onClose}
style={{
padding: '0px 7px 0px 0px',
marginRight: 3,
display: 'flex',
justifyContent: 'start',
alignItems: 'center',
}}
>
{label}
</Tag>
);
};
function handleBlur() {
setInputValue('');
}
return (
<Modal
title="Add Owners"
visible={visible}
visible
onCancel={onModalClose}
keyboard
footer={
@ -252,27 +228,33 @@ export const AddOwnersModal = ({
</>
}
>
<Form layout="vertical" form={form} colon={false}>
<Form layout="vertical" colon={false}>
<Form.Item key="owners" name="owners" label={<Typography.Text strong>Owner</Typography.Text>}>
<Typography.Paragraph>Find a user or group</Typography.Paragraph>
<Form.Item name="owner">
<SelectInput
labelInValue
value={selectedOwners}
autoFocus
defaultOpen
mode="multiple"
filterOption={false}
ref={inputEl}
placeholder="Search for users or groups..."
showSearch
filterOption={false}
defaultActiveFirstOption={false}
onSelect={(asset: any) => onSelectOwner(asset)}
onDeselect={(asset: any) => onDeselectOwner(asset)}
onSearch={handleActorSearch}
onSearch={(value: string) => {
// eslint-disable-next-line react/prop-types
handleActorSearch(value.trim());
// eslint-disable-next-line react/prop-types
setInputValue(value.trim());
}}
tagRender={tagRender}
onBlur={handleBlur}
value={selectedOwners}
>
{combinedSearchResults?.map((result) => (
<Select.Option key={result?.entity?.urn} value={result.entity.urn}>
{renderSearchResult(result)}
</Select.Option>
))}
{ownerSearchOptions}
</SelectInput>
</Form.Item>
</Form.Item>

View File

@ -32,17 +32,18 @@ export const SidebarOwnerSection = ({ properties }: { properties?: any }) => {
<PlusOutlined /> Add Owners
</Button>
</div>
<AddOwnersModal
urn={mutationUrn}
defaultOwnerType={properties?.defaultOwnerType}
hideOwnerType={properties?.hideOwnerType || false}
type={entityType}
visible={showAddModal}
refetch={refetch}
onCloseModal={() => {
setShowAddModal(false);
}}
/>
{showAddModal && (
<AddOwnersModal
urn={mutationUrn}
defaultOwnerType={properties?.defaultOwnerType}
hideOwnerType={properties?.hideOwnerType || false}
type={entityType}
refetch={refetch}
onCloseModal={() => {
setShowAddModal(false);
}}
/>
)}
</div>
);
};

View File

@ -0,0 +1,29 @@
import React from 'react';
import styled from 'styled-components';
const DomainContainerWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
`;
const DomainContentWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
type Props = {
name: string;
};
export const DomainLabel = ({ name }: Props) => {
return (
<DomainContainerWrapper>
<DomainContentWrapper>
<div>{name}</div>
</DomainContentWrapper>
</DomainContainerWrapper>
);
};

View File

@ -0,0 +1,34 @@
import React from 'react';
import styled from 'styled-components';
import { EntityType } from '../../types.generated';
import { CustomAvatar } from './avatar';
const OwnerContainerWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: 2px;
`;
const OwnerContentWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
type Props = {
name: string;
avatarUrl: string | undefined;
type: EntityType;
};
export const OwnerLabel = ({ name, avatarUrl, type }: Props) => {
return (
<OwnerContainerWrapper>
<OwnerContentWrapper>
<CustomAvatar size={24} name={name} photoUrl={avatarUrl} isGroup={type === EntityType.CorpGroup} />
<div>{name}</div>
</OwnerContentWrapper>
</OwnerContainerWrapper>
);
};

View File

@ -414,16 +414,17 @@ export default function TagStyleEntity({ urn, useGetSearchResults = useWrappedSe
</Button>
</div>
<div>
<AddOwnersModal
hideOwnerType
visible={showAddModal}
refetch={refetch}
onCloseModal={() => {
setShowAddModal(false);
}}
urn={urn}
type={EntityType.Tag}
/>
{showAddModal && (
<AddOwnersModal
hideOwnerType
refetch={refetch}
onCloseModal={() => {
setShowAddModal(false);
}}
urn={urn}
type={EntityType.Tag}
/>
)}
</div>
</div>
</DetailsLayout>

View File

@ -0,0 +1,18 @@
import { useGetSearchResultsForMultipleQuery } from '../../graphql/search.generated';
import { EntityType } from '../../types.generated';
export const useGetRecommendations = (types: Array<EntityType>) => {
const { data } = useGetSearchResultsForMultipleQuery({
variables: {
input: {
types,
query: '*',
start: 0,
count: 5,
},
},
});
const recommendedData = data?.searchAcrossEntities?.searchResults?.map((searchResult) => searchResult.entity) || [];
return [recommendedData];
};

View File

@ -1,11 +1,10 @@
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { message, Button, Modal, Select, Typography, Tag as CustomTag } from 'antd';
import styled from 'styled-components';
import { useGetSearchResultsLazyQuery } from '../../../graphql/search.generated';
import { EntityType, SubResourceType, SearchResult, Tag } from '../../../types.generated';
import { EntityType, SubResourceType, Tag, Entity } from '../../../types.generated';
import CreateTagModal from './CreateTagModal';
import { useEntityRegistry } from '../../useEntityRegistry';
import { useAddTagsMutation, useAddTermsMutation } from '../../../graphql/mutations.generated';
import analytics, { EventType, EntityActionType } from '../../analytics';
import { useEnterKeyListener } from '../useEnterKeyListener';
@ -13,6 +12,8 @@ import TermLabel from '../TermLabel';
import TagLabel from '../TagLabel';
import GlossaryBrowser from '../../glossary/GlossaryBrowser/GlossaryBrowser';
import ClickOutside from '../ClickOutside';
import { useEntityRegistry } from '../../useEntityRegistry';
import { useGetRecommendations } from '../recommendation';
type AddTagsModalProps = {
visible: boolean;
@ -27,6 +28,17 @@ const TagSelect = styled(Select)`
width: 480px;
`;
const StyleTag = styled(CustomTag)`
margin-right: 3px;
display: flex;
justify-content: start;
align-items: center;
white-space: nowrap;
opacity: 1;
color: #434343;
line-height: 16px;
`;
export const BrowserWrapper = styled.div<{ isHidden: boolean }>`
background-color: white;
border-radius: 5px;
@ -61,13 +73,16 @@ export default function AddTagsTermsModal({
const [disableAdd, setDisableAdd] = useState(false);
const [urns, setUrns] = useState<string[]>([]);
const [selectedTerms, setSelectedTerms] = useState<any[]>([]);
const [selectedTags, setSelectedTags] = useState<any[]>([]);
const [isFocusedOnInput, setIsFocusedOnInput] = useState(false);
const [addTagsMutation] = useAddTagsMutation();
const [addTermsMutation] = useAddTermsMutation();
const [tagTermSearch, { data: tagTermSearchData }] = useGetSearchResultsLazyQuery();
const tagSearchResults = tagTermSearchData?.search?.searchResults || [];
const tagSearchResults = tagTermSearchData?.search?.searchResults?.map((searchResult) => searchResult.entity) || [];
const [recommendedData] = useGetRecommendations([EntityType.Tag]);
const inputEl = useRef(null);
const handleSearch = (text: string) => {
if (text.length > 0) {
@ -84,39 +99,40 @@ export default function AddTagsTermsModal({
}
};
const renderSearchResult = (result: SearchResult) => {
const renderSearchResult = (entity: Entity) => {
const displayName =
result.entity.type === EntityType.Tag
? (result.entity as Tag).name
: entityRegistry.getDisplayName(result.entity.type, result.entity);
entity.type === EntityType.Tag ? (entity as Tag).name : entityRegistry.getDisplayName(entity.type, entity);
const tagOrTermComponent =
result.entity.type === EntityType.Tag ? (
entity.type === EntityType.Tag ? (
<TagLabel
name={displayName}
colorHash={(result.entity as Tag).urn}
color={(result.entity as Tag).properties?.colorHex}
colorHash={(entity as Tag).urn}
color={(entity as Tag).properties?.colorHex}
/>
) : (
<TermLabel name={displayName} />
);
return (
<Select.Option value={result.entity.urn} key={result.entity.urn} name={displayName}>
<Select.Option value={entity.urn} key={entity.urn} name={displayName}>
{tagOrTermComponent}
</Select.Option>
);
};
const tagSearchOptions = tagSearchResults.map((result) => {
const tagResult =
(!inputValue || inputValue.length === 0) && type === EntityType.Tag ? recommendedData : tagSearchResults;
const tagSearchOptions = tagResult?.map((result) => {
return renderSearchResult(result);
});
const inputExistsInTagSearch = tagSearchResults.some((result: SearchResult) => {
const displayName = entityRegistry.getDisplayName(result.entity.type, result.entity);
const inputExistsInTagSearch = tagSearchResults.some((entity: Entity) => {
const displayName = entityRegistry.getDisplayName(entity.type, entity);
return displayName.toLowerCase() === inputValue.toLowerCase();
});
if (!inputExistsInTagSearch && inputValue.length > 0 && type === EntityType.Tag && urns.length === 0) {
tagSearchOptions.push(
tagSearchOptions?.push(
<Select.Option value={CREATE_TAG_VALUE} key={CREATE_TAG_VALUE}>
<Typography.Link> Create {inputValue}</Typography.Link>
</Select.Option>,
@ -125,32 +141,20 @@ export default function AddTagsTermsModal({
const tagRender = (props) => {
// eslint-disable-next-line react/prop-types
const { label, closable, onClose, value } = props;
const { closable, onClose, value } = props;
const onPreventMouseDown = (event) => {
event.preventDefault();
event.stopPropagation();
};
const selectedItem =
type === EntityType.GlossaryTerm ? selectedTerms.find((term) => term.urn === value).component : label;
type === EntityType.GlossaryTerm
? selectedTerms.find((term) => term.urn === value).component
: selectedTags.find((term) => term.urn === value).component;
return (
<CustomTag
onMouseDown={onPreventMouseDown}
closable={closable}
onClose={onClose}
style={{
marginRight: 3,
display: 'flex',
justifyContent: 'start',
alignItems: 'center',
whiteSpace: 'nowrap',
opacity: 1,
color: '#434343',
lineHeight: '16px',
}}
>
<StyleTag onMouseDown={onPreventMouseDown} closable={closable} onClose={onClose}>
{selectedItem}
</CustomTag>
</StyleTag>
);
};
@ -179,9 +183,26 @@ export default function AddTagsTermsModal({
return;
}
const newUrns = [...(urns || []), urn];
const selectedSearchOption = tagSearchOptions?.find((option) => option.props.value === urn);
const selectedTagOption = tagResult?.find((tag) => tag.urn === urn);
setUrns(newUrns);
const selectedSearchOption = tagSearchOptions.find((option) => option.props.value === urn);
setSelectedTerms([...selectedTerms, { urn, component: <TermLabel name={selectedSearchOption?.props.name} /> }]);
setSelectedTags([
...selectedTags,
{
urn,
component: (
<TagLabel
name={selectedSearchOption?.props.name}
colorHash={(selectedTagOption as Tag).urn}
color={(selectedTagOption as Tag).properties?.colorHex}
/>
),
},
]);
if (inputEl && inputEl.current) {
(inputEl.current as any).blur();
}
};
// When a Tag or term search result is deselected, remove the urn from the Owners
@ -191,6 +212,7 @@ export default function AddTagsTermsModal({
setInputValue('');
setIsFocusedOnInput(true);
setSelectedTerms(selectedTerms.filter((term) => term.urn !== urn));
setSelectedTags(selectedTags.filter((term) => term.urn !== urn));
};
// Function to handle the modal action's
@ -313,7 +335,9 @@ export default function AddTagsTermsModal({
<ClickOutside onClickOutside={() => setIsFocusedOnInput(false)}>
<TagSelect
autoFocus
defaultOpen
mode="multiple"
ref={inputEl}
filterOption={false}
placeholder={`Search for ${entityRegistry.getEntityName(type)?.toLowerCase()}...`}
showSearch
@ -331,7 +355,7 @@ export default function AddTagsTermsModal({
onClear={clearInput}
onFocus={() => setIsFocusedOnInput(true)}
onBlur={handleBlur}
dropdownStyle={isShowingGlossaryBrowser || !inputValue ? { display: 'none' } : {}}
dropdownStyle={isShowingGlossaryBrowser ? { display: 'none' } : {}}
>
{tagSearchOptions}
</TagSelect>