mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-26 09:26:22 +00:00
feat(advanced-search): adding select value modal (#6026)
* adding select value modal * responding to comments
This commit is contained in:
parent
3c3ab64954
commit
60928757e0
@ -149,7 +149,7 @@ export class ContainerEntity implements Entity<Container> {
|
||||
};
|
||||
|
||||
displayName = (data: Container) => {
|
||||
return data?.properties?.name || data?.urn;
|
||||
return data?.properties?.name || data?.properties?.qualifiedName || data?.urn;
|
||||
};
|
||||
|
||||
getOverridePropertiesFromEntity = (data: Container) => {
|
||||
|
||||
@ -48,7 +48,6 @@ export default function GlossaryTermsDropdown({ urns, disabled = false, refetch
|
||||
resourceUrn: urn,
|
||||
}))}
|
||||
operationType={operationType}
|
||||
entityType={EntityType.Dataset} // TODO REMOVE
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@ -47,7 +47,6 @@ export default function TagsDropdown({ urns, disabled = false, refetch }: Props)
|
||||
resources={urns.map((urn) => ({
|
||||
resourceUrn: urn,
|
||||
}))}
|
||||
entityType={EntityType.DataFlow}
|
||||
operationType={operationType}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -0,0 +1,189 @@
|
||||
import { Button, Form, Modal, Select, Tag, Tooltip } from 'antd';
|
||||
import React, { ReactNode, useRef, useState } from 'react';
|
||||
import styled from 'styled-components/macro';
|
||||
import { useGetSearchResultsLazyQuery } from '../../../../../../../graphql/search.generated';
|
||||
import { Container, Entity, EntityType } from '../../../../../../../types.generated';
|
||||
import { useEnterKeyListener } from '../../../../../../shared/useEnterKeyListener';
|
||||
import { useEntityRegistry } from '../../../../../../useEntityRegistry';
|
||||
|
||||
type Props = {
|
||||
onCloseModal: () => void;
|
||||
defaultValues?: { urn: string; entity?: Entity | null }[];
|
||||
onOkOverride?: (result: string[]) => void;
|
||||
titleOverride?: string;
|
||||
};
|
||||
|
||||
type SelectedContainer = {
|
||||
entity?: Entity | null;
|
||||
urn: string;
|
||||
};
|
||||
|
||||
const StyleTag = styled(Tag)`
|
||||
padding: 0px 7px;
|
||||
margin-right: 3px;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const PreviewImage = styled.img`
|
||||
max-height: 18px;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
background-color: transparent;
|
||||
margin-right: 4px;
|
||||
`;
|
||||
|
||||
export const ContainerSelectModal = ({ onCloseModal, defaultValues, onOkOverride, titleOverride }: Props) => {
|
||||
const [containerSearch, { data: platforSearchData }] = useGetSearchResultsLazyQuery();
|
||||
const entityRegistry = useEntityRegistry();
|
||||
|
||||
const containerSearchResults =
|
||||
platforSearchData?.search?.searchResults?.map((searchResult) => searchResult.entity) || [];
|
||||
|
||||
const [selectedContainers, setSelectedContainers] = useState<SelectedContainer[]>(defaultValues || []);
|
||||
|
||||
const inputEl = useRef(null);
|
||||
|
||||
const onModalClose = () => {
|
||||
onCloseModal();
|
||||
};
|
||||
|
||||
const handleSearch = (text: string) => {
|
||||
containerSearch({
|
||||
variables: {
|
||||
input: {
|
||||
type: EntityType.Container,
|
||||
query: text,
|
||||
start: 0,
|
||||
count: 5,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Renders a search result in the select dropdown.
|
||||
const renderSearchResult = (entity: Container) => {
|
||||
const displayName = entityRegistry.getDisplayName(EntityType.Container, entity);
|
||||
|
||||
const truncatedDisplayName = displayName.length > 25 ? `${displayName.slice(0, 25)}...` : displayName;
|
||||
return (
|
||||
<Tooltip title={displayName}>
|
||||
<PreviewImage src={entity.platform?.properties?.logoUrl || undefined} alt={entity.properties?.name} />
|
||||
<span>{truncatedDisplayName}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const containerSearchOptions = containerSearchResults?.map((result) => {
|
||||
return (
|
||||
<Select.Option value={result.urn} key={result.urn}>
|
||||
{renderSearchResult(result as Container)}
|
||||
</Select.Option>
|
||||
);
|
||||
});
|
||||
|
||||
const onSelectContainer = (newValue: { value: string; label: ReactNode }) => {
|
||||
const newUrn = newValue.value;
|
||||
|
||||
if (inputEl && inputEl.current) {
|
||||
(inputEl.current as any).blur();
|
||||
}
|
||||
|
||||
const filteredContainer = containerSearchResults?.find((entity) => entity.urn === newUrn);
|
||||
|
||||
if (filteredContainer) {
|
||||
const container = filteredContainer as Container;
|
||||
setSelectedContainers([
|
||||
...(selectedContainers || []),
|
||||
{
|
||||
entity: container,
|
||||
urn: newUrn,
|
||||
},
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const onDeselectContainer = (val) => {
|
||||
setSelectedContainers(selectedContainers?.filter((container) => container.urn !== val.value));
|
||||
};
|
||||
|
||||
const onOk = async () => {
|
||||
if (!selectedContainers) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onOkOverride) {
|
||||
onOkOverride(selectedContainers?.map((container) => container.urn));
|
||||
}
|
||||
};
|
||||
|
||||
// Handle the Enter press
|
||||
useEnterKeyListener({
|
||||
querySelectorToExecuteClick: '#setContainerButton',
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={titleOverride || 'Select Container'}
|
||||
visible
|
||||
onCancel={onModalClose}
|
||||
footer={
|
||||
<>
|
||||
<Button onClick={onModalClose} type="text">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button id="setContainerButton" disabled={selectedContainers?.length === 0} onClick={onOk}>
|
||||
Add
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Form component={false}>
|
||||
<Form.Item>
|
||||
<Select
|
||||
autoFocus
|
||||
filterOption={false}
|
||||
showSearch
|
||||
mode="multiple"
|
||||
defaultActiveFirstOption={false}
|
||||
placeholder="Search for Containers..."
|
||||
onSelect={(containerUrn: any) => onSelectContainer(containerUrn)}
|
||||
onDeselect={onDeselectContainer}
|
||||
onSearch={(value: string) => {
|
||||
// eslint-disable-next-line react/prop-types
|
||||
handleSearch(value.trim());
|
||||
}}
|
||||
ref={inputEl}
|
||||
labelInValue
|
||||
value={selectedContainers?.map((container) => ({
|
||||
value: container.urn,
|
||||
label: container.entity ? (
|
||||
renderSearchResult(container.entity as Container)
|
||||
) : (
|
||||
<span>{container.urn}</span>
|
||||
),
|
||||
}))}
|
||||
tagRender={tagRender}
|
||||
>
|
||||
{containerSearchOptions}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@ -14,6 +14,9 @@ type Props = {
|
||||
urns: string[];
|
||||
onCloseModal: () => void;
|
||||
refetch?: () => Promise<any>;
|
||||
defaultValue?: { urn: string; entity?: Entity | null };
|
||||
onOkOverride?: (result: string) => void;
|
||||
titleOverride?: string;
|
||||
};
|
||||
|
||||
type SelectedDomain = {
|
||||
@ -30,10 +33,18 @@ const StyleTag = styled(Tag)`
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const SetDomainModal = ({ urns, onCloseModal, refetch }: Props) => {
|
||||
export const SetDomainModal = ({ urns, onCloseModal, refetch, defaultValue, onOkOverride, titleOverride }: Props) => {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [selectedDomain, setSelectedDomain] = useState<SelectedDomain | undefined>(undefined);
|
||||
const [selectedDomain, setSelectedDomain] = useState<SelectedDomain | undefined>(
|
||||
defaultValue
|
||||
? {
|
||||
displayName: entityRegistry.getDisplayName(EntityType.Domain, defaultValue?.entity),
|
||||
type: EntityType.Domain,
|
||||
urn: defaultValue?.urn,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
const [domainSearch, { data: domainSearchData }] = useGetSearchResultsLazyQuery();
|
||||
const domainSearchResults =
|
||||
domainSearchData?.search?.searchResults?.map((searchResult) => searchResult.entity) || [];
|
||||
@ -100,6 +111,12 @@ export const SetDomainModal = ({ urns, onCloseModal, refetch }: Props) => {
|
||||
if (!selectedDomain) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onOkOverride) {
|
||||
onOkOverride(selectedDomain?.urn);
|
||||
return;
|
||||
}
|
||||
|
||||
batchSetDomainMutation({
|
||||
variables: {
|
||||
input: {
|
||||
@ -149,7 +166,7 @@ export const SetDomainModal = ({ urns, onCloseModal, refetch }: Props) => {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Set Domain"
|
||||
title={titleOverride || 'Set Domain'}
|
||||
visible
|
||||
onCancel={onModalClose}
|
||||
footer={
|
||||
|
||||
@ -41,12 +41,18 @@ type Props = {
|
||||
onCloseModal: () => void;
|
||||
refetch?: () => Promise<any>;
|
||||
entityType?: EntityType; // Only used for tracking events
|
||||
onOkOverride?: (result: SelectedOwner[]) => void;
|
||||
title?: string;
|
||||
defaultValues?: { urn: string; entity?: Entity | null }[];
|
||||
};
|
||||
|
||||
// value: {ownerUrn: string, ownerEntityType: EntityType}
|
||||
type SelectedOwner = {
|
||||
label: string;
|
||||
value;
|
||||
label: string | React.ReactNode;
|
||||
value: {
|
||||
ownerUrn: string;
|
||||
ownerEntityType: EntityType;
|
||||
};
|
||||
};
|
||||
|
||||
export const EditOwnersModal = ({
|
||||
@ -57,13 +63,50 @@ export const EditOwnersModal = ({
|
||||
onCloseModal,
|
||||
refetch,
|
||||
entityType,
|
||||
onOkOverride,
|
||||
title,
|
||||
defaultValues,
|
||||
}: Props) => {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
|
||||
// Renders a search result in the select dropdown.
|
||||
const renderSearchResult = (entity: Entity) => {
|
||||
const avatarUrl =
|
||||
(entity.type === EntityType.CorpUser && (entity as CorpUser).editableProperties?.pictureLink) || undefined;
|
||||
const displayName = entityRegistry.getDisplayName(entity.type, entity);
|
||||
return (
|
||||
<Select.Option value={entity.urn} key={entity.urn}>
|
||||
<OwnerLabel name={displayName} avatarUrl={avatarUrl} type={entity.type} />
|
||||
</Select.Option>
|
||||
);
|
||||
};
|
||||
|
||||
const renderDropdownResult = (entity: Entity) => {
|
||||
const avatarUrl =
|
||||
entity.type === EntityType.CorpUser
|
||||
? (entity as CorpUser).editableProperties?.pictureLink || undefined
|
||||
: undefined;
|
||||
const displayName = entityRegistry.getDisplayName(entity.type, entity);
|
||||
return <OwnerLabel name={displayName} avatarUrl={avatarUrl} type={entity.type} />;
|
||||
};
|
||||
|
||||
const defaultValuesToSelectedOwners = (vals: { urn: string; entity?: Entity | null }[]): SelectedOwner[] => {
|
||||
return vals.map((defaultValue) => ({
|
||||
label: defaultValue.entity ? renderDropdownResult(defaultValue.entity) : defaultValue.urn,
|
||||
value: {
|
||||
ownerUrn: defaultValue.urn,
|
||||
ownerEntityType: defaultValue.entity?.type || EntityType.CorpUser,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [batchAddOwnersMutation] = useBatchAddOwnersMutation();
|
||||
const [batchRemoveOwnersMutation] = useBatchRemoveOwnersMutation();
|
||||
const ownershipTypes = OWNERSHIP_DISPLAY_TYPES;
|
||||
const [selectedOwners, setSelectedOwners] = useState<SelectedOwner[]>([]);
|
||||
const [selectedOwners, setSelectedOwners] = useState<SelectedOwner[]>(
|
||||
defaultValuesToSelectedOwners(defaultValues || []),
|
||||
);
|
||||
const [selectedOwnerType, setSelectedOwnerType] = useState<OwnershipType>(defaultOwnerType || OwnershipType.None);
|
||||
|
||||
// User and group dropdown search results!
|
||||
@ -101,20 +144,6 @@ export const EditOwnersModal = ({
|
||||
handleSearch(EntityType.CorpGroup, text, groupSearch);
|
||||
};
|
||||
|
||||
// Renders a search result in the select dropdown.
|
||||
const renderSearchResult = (entity: Entity) => {
|
||||
const avatarUrl =
|
||||
entity.type === EntityType.CorpUser
|
||||
? (entity as CorpUser).editableProperties?.pictureLink || undefined
|
||||
: undefined;
|
||||
const displayName = entityRegistry.getDisplayName(entity.type, entity);
|
||||
return (
|
||||
<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) => {
|
||||
@ -149,7 +178,7 @@ export const EditOwnersModal = ({
|
||||
label: selectedValue.value,
|
||||
value: {
|
||||
ownerUrn: selectedValue.value,
|
||||
ownerEntityType,
|
||||
ownerEntityType: ownerEntityType as unknown as EntityType,
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -160,7 +189,9 @@ export const EditOwnersModal = ({
|
||||
// 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);
|
||||
const newValues = selectedOwners.filter(
|
||||
(owner) => owner.label !== selectedValue.value && owner.value.ownerUrn !== selectedValue.value,
|
||||
);
|
||||
setSelectedOwners(newValues);
|
||||
};
|
||||
|
||||
@ -251,6 +282,12 @@ export const EditOwnersModal = ({
|
||||
if (selectedOwners.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onOkOverride) {
|
||||
onOkOverride(selectedOwners);
|
||||
return;
|
||||
}
|
||||
|
||||
const inputs = selectedOwners.map((selectedActor) => {
|
||||
const input = {
|
||||
ownerUrn: selectedActor.value.ownerUrn,
|
||||
@ -273,7 +310,7 @@ export const EditOwnersModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={`${operationType === OperationType.ADD ? 'Add' : 'Remove'} Owners`}
|
||||
title={title || `${operationType === OperationType.ADD ? 'Add' : 'Remove'} Owners`}
|
||||
visible
|
||||
onCancel={onModalClose}
|
||||
keyboard
|
||||
@ -312,7 +349,12 @@ export const EditOwnersModal = ({
|
||||
}}
|
||||
tagRender={tagRender}
|
||||
onBlur={handleBlur}
|
||||
value={selectedOwners}
|
||||
value={selectedOwners as any}
|
||||
defaultValue={selectedOwners.map((owner) => ({
|
||||
key: owner.value.ownerUrn,
|
||||
value: owner.value.ownerUrn,
|
||||
label: owner.label,
|
||||
}))}
|
||||
>
|
||||
{ownerSearchOptions}
|
||||
</SelectInput>
|
||||
|
||||
@ -8,7 +8,7 @@ import { useEnterKeyListener } from '../../../../../../shared/useEnterKeyListene
|
||||
type Props = {
|
||||
onCloseModal: () => void;
|
||||
defaultValues?: { urn: string; entity?: Entity | null }[];
|
||||
onOkOverride?: (result: string[]) => void;
|
||||
onOk?: (result: string[]) => void;
|
||||
titleOverride?: string;
|
||||
};
|
||||
|
||||
@ -33,7 +33,7 @@ const PreviewImage = styled.img`
|
||||
margin-right: 4px;
|
||||
`;
|
||||
|
||||
export const SelectPlatformModal = ({ onCloseModal, defaultValues, onOkOverride, titleOverride }: Props) => {
|
||||
export const SelectPlatformModal = ({ onCloseModal, defaultValues, onOk, titleOverride }: Props) => {
|
||||
const [platformSearch, { data: platforSearchData }] = useGetSearchResultsLazyQuery();
|
||||
const platformSearchResults =
|
||||
platforSearchData?.search?.searchResults?.map((searchResult) => searchResult.entity) || [];
|
||||
@ -102,16 +102,16 @@ export const SelectPlatformModal = ({ onCloseModal, defaultValues, onOkOverride,
|
||||
};
|
||||
|
||||
const onDeselectPlatform = (val) => {
|
||||
setSelectedPlatforms(selectedPlatforms?.filter((platform) => platform.urn !== val));
|
||||
setSelectedPlatforms(selectedPlatforms?.filter((platform) => platform.urn !== val.value));
|
||||
};
|
||||
|
||||
const onOk = async () => {
|
||||
const handleOk = async () => {
|
||||
if (!selectedPlatforms) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onOkOverride) {
|
||||
onOkOverride(selectedPlatforms?.map((platform) => platform.urn));
|
||||
if (onOk) {
|
||||
onOk(selectedPlatforms?.map((platform) => platform.urn));
|
||||
}
|
||||
};
|
||||
|
||||
@ -144,7 +144,7 @@ export const SelectPlatformModal = ({ onCloseModal, defaultValues, onOkOverride,
|
||||
<Button onClick={onModalClose} type="text">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button id="setPlatformButton" disabled={selectedPlatforms?.length === 0} onClick={onOk}>
|
||||
<Button id="setPlatformButton" disabled={selectedPlatforms?.length === 0} onClick={handleOk}>
|
||||
Add
|
||||
</Button>
|
||||
</>
|
||||
|
||||
@ -0,0 +1,211 @@
|
||||
import React from 'react';
|
||||
import { FacetMetadata, EntityType } from '../../types.generated';
|
||||
import { ContainerSelectModal } from '../entity/shared/containers/profile/sidebar/Container/ContainerSelectModal';
|
||||
import { SetDomainModal } from '../entity/shared/containers/profile/sidebar/Domain/SetDomainModal';
|
||||
import { EditOwnersModal } from '../entity/shared/containers/profile/sidebar/Ownership/EditOwnersModal';
|
||||
import { SelectPlatformModal } from '../entity/shared/containers/profile/sidebar/Platform/SelectPlatformModal';
|
||||
import EditTagTermsModal from '../shared/tags/AddTagsTermsModal';
|
||||
import { ChooseEntityTypeModal } from './ChooseEntityTypeModal';
|
||||
import { EditTextModal } from './EditTextModal';
|
||||
|
||||
type Props = {
|
||||
facet?: FacetMetadata | null;
|
||||
filterField: string;
|
||||
onSelect: (values: string[]) => void;
|
||||
onCloseModal: () => void;
|
||||
initialValues?: string[];
|
||||
};
|
||||
|
||||
export const AdvancedFilterSelectValueModal = ({
|
||||
filterField,
|
||||
onSelect,
|
||||
onCloseModal,
|
||||
initialValues,
|
||||
facet,
|
||||
}: Props) => {
|
||||
if (filterField === 'owners') {
|
||||
return (
|
||||
<EditOwnersModal
|
||||
title="Select Owners"
|
||||
urns={[]}
|
||||
defaultValues={initialValues?.map((urn) => ({
|
||||
urn,
|
||||
entity: facet?.aggregations.find((aggregation) => aggregation.value === urn)?.entity,
|
||||
}))}
|
||||
onCloseModal={onCloseModal}
|
||||
hideOwnerType
|
||||
onOkOverride={(owners) => {
|
||||
onSelect(owners.map((owner) => owner.value.ownerUrn));
|
||||
onCloseModal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (filterField === 'domains') {
|
||||
return (
|
||||
<SetDomainModal
|
||||
titleOverride="Select Domain"
|
||||
urns={[]}
|
||||
defaultValue={
|
||||
initialValues?.map((urn) => ({
|
||||
urn,
|
||||
entity: facet?.aggregations.find((aggregation) => aggregation.value === urn)?.entity,
|
||||
}))?.[0]
|
||||
}
|
||||
onCloseModal={onCloseModal}
|
||||
onOkOverride={(domainUrn) => {
|
||||
onSelect([domainUrn]);
|
||||
onCloseModal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filterField === 'container') {
|
||||
return (
|
||||
<ContainerSelectModal
|
||||
titleOverride="Select Container"
|
||||
defaultValues={initialValues?.map((urn) => ({
|
||||
urn,
|
||||
entity: facet?.aggregations.find((aggregation) => aggregation.value === urn)?.entity,
|
||||
}))}
|
||||
onCloseModal={onCloseModal}
|
||||
onOkOverride={(containerUrns) => {
|
||||
onSelect(containerUrns);
|
||||
onCloseModal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filterField === 'platform') {
|
||||
return (
|
||||
<SelectPlatformModal
|
||||
defaultValues={initialValues?.map((urn) => ({
|
||||
urn,
|
||||
entity: facet?.aggregations.find((aggregation) => aggregation.value === urn)?.entity,
|
||||
}))}
|
||||
titleOverride="Select Platform"
|
||||
onCloseModal={onCloseModal}
|
||||
onOk={(platformUrns) => {
|
||||
onSelect(platformUrns);
|
||||
onCloseModal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filterField === 'fieldPaths') {
|
||||
return (
|
||||
<EditTextModal
|
||||
title="Filter by Column"
|
||||
defaultValue={initialValues?.[0]}
|
||||
onCloseModal={onCloseModal}
|
||||
onOk={(newValue) => {
|
||||
onSelect([newValue]);
|
||||
onCloseModal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filterField === 'description' || filterField === 'fieldDescriptions') {
|
||||
return (
|
||||
<EditTextModal
|
||||
title="Filter by Description"
|
||||
defaultValue={initialValues?.[0]}
|
||||
onCloseModal={onCloseModal}
|
||||
onOk={(newValue) => {
|
||||
onSelect([newValue]);
|
||||
onCloseModal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filterField === 'origin') {
|
||||
return (
|
||||
<EditTextModal
|
||||
title="Filter by Environment"
|
||||
defaultValue={initialValues?.[0]}
|
||||
onCloseModal={onCloseModal}
|
||||
onOk={(newValue) => {
|
||||
onSelect([newValue]);
|
||||
onCloseModal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filterField === 'typeNames') {
|
||||
return (
|
||||
<EditTextModal
|
||||
title="Filter by Subtype"
|
||||
defaultValue={initialValues?.[0]}
|
||||
onCloseModal={onCloseModal}
|
||||
onOk={(newValue) => {
|
||||
onSelect([newValue]);
|
||||
onCloseModal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filterField === 'entity') {
|
||||
return (
|
||||
<ChooseEntityTypeModal
|
||||
title="Filter by Entity Type"
|
||||
defaultValue={initialValues?.[0]}
|
||||
onCloseModal={onCloseModal}
|
||||
onOk={(newValue) => {
|
||||
onSelect([newValue]);
|
||||
onCloseModal();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filterField === 'tags' || filterField === 'fieldTags') {
|
||||
return (
|
||||
<EditTagTermsModal
|
||||
resources={[]}
|
||||
type={EntityType.Tag}
|
||||
visible
|
||||
onCloseModal={onCloseModal}
|
||||
onOkOverride={(urns) => {
|
||||
onSelect(urns);
|
||||
onCloseModal();
|
||||
}}
|
||||
defaultValues={initialValues?.map((urn) => ({
|
||||
urn,
|
||||
entity: facet?.aggregations.find((aggregation) => aggregation.value === urn)?.entity,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (filterField === 'removed') {
|
||||
onSelect(['true']);
|
||||
onCloseModal();
|
||||
}
|
||||
|
||||
if (filterField === 'glossaryTerms' || filterField === 'fieldGlossaryTerms') {
|
||||
return (
|
||||
<EditTagTermsModal
|
||||
resources={[]}
|
||||
type={EntityType.GlossaryTerm}
|
||||
visible
|
||||
onCloseModal={onCloseModal}
|
||||
onOkOverride={(urns) => {
|
||||
onSelect(urns);
|
||||
onCloseModal();
|
||||
}}
|
||||
defaultValues={initialValues?.map((urn) => ({
|
||||
urn,
|
||||
entity: facet?.aggregations.find((aggregation) => aggregation.value === urn)?.entity,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
48
datahub-web-react/src/app/search/ChooseEntityTypeModal.tsx
Normal file
48
datahub-web-react/src/app/search/ChooseEntityTypeModal.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { Button, Modal, Select } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { useEntityRegistry } from '../useEntityRegistry';
|
||||
|
||||
type Props = {
|
||||
onCloseModal: () => void;
|
||||
onOk?: (result: string) => void;
|
||||
title?: string;
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
export const ChooseEntityTypeModal = ({ defaultValue, onCloseModal, onOk, title }: Props) => {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
const entityTypes = entityRegistry.getSearchEntityTypes();
|
||||
|
||||
const [stagedValue, setStagedValue] = useState(defaultValue || entityTypes[0]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={title}
|
||||
visible
|
||||
onCancel={onCloseModal}
|
||||
keyboard
|
||||
footer={
|
||||
<>
|
||||
<Button onClick={onCloseModal} type="text">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={stagedValue.length === 0} onClick={() => onOk?.(stagedValue)}>
|
||||
Done
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
onChange={(newValue) => setStagedValue(newValue)}
|
||||
value={stagedValue}
|
||||
dropdownMatchSelectWidth={false}
|
||||
>
|
||||
{entityTypes.map((type) => (
|
||||
<Option value={type}>{entityRegistry.getCollectionName(type)}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
33
datahub-web-react/src/app/search/EditTextModal.tsx
Normal file
33
datahub-web-react/src/app/search/EditTextModal.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Button, Input, Modal } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
type Props = {
|
||||
onCloseModal: () => void;
|
||||
onOk?: (result: string) => void;
|
||||
title?: string;
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export const EditTextModal = ({ defaultValue, onCloseModal, onOk, title }: Props) => {
|
||||
const [stagedValue, setStagedValue] = useState(defaultValue || '');
|
||||
return (
|
||||
<Modal
|
||||
title={title}
|
||||
visible
|
||||
onCancel={onCloseModal}
|
||||
keyboard
|
||||
footer={
|
||||
<>
|
||||
<Button onClick={onCloseModal} type="text">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={stagedValue.trim().length === 0} onClick={() => onOk?.(stagedValue)}>
|
||||
Done
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Input onChange={(e) => setStagedValue(e.target.value)} value={stagedValue} />
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@ -12,13 +12,12 @@ import {
|
||||
useBatchRemoveTermsMutation,
|
||||
} from '../../../graphql/mutations.generated';
|
||||
import { useEnterKeyListener } from '../useEnterKeyListener';
|
||||
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';
|
||||
import { FORBIDDEN_URN_CHARS_REGEX } from '../../entity/shared/utils';
|
||||
import { TagTermLabel } from './TagTermLabel';
|
||||
|
||||
export enum OperationType {
|
||||
ADD,
|
||||
@ -29,10 +28,10 @@ type EditTagsModalProps = {
|
||||
visible: boolean;
|
||||
onCloseModal: () => void;
|
||||
resources: ResourceRefInput[];
|
||||
// eslint-disable-next-line
|
||||
entityType: EntityType;
|
||||
type?: EntityType;
|
||||
operationType?: OperationType;
|
||||
defaultValues?: { urn: string; entity?: Entity | null }[];
|
||||
onOkOverride?: (result: string[]) => void;
|
||||
};
|
||||
|
||||
const TagSelect = styled(Select)`
|
||||
@ -74,20 +73,37 @@ const isValidTagName = (tagName: string) => {
|
||||
return tagName && tagName.length > 0 && !FORBIDDEN_URN_CHARS_REGEX.test(tagName);
|
||||
};
|
||||
|
||||
const defaultValuesToSelectedValue = (defaultValues?: { urn: string; entity?: Entity | null }[]): any[] => {
|
||||
return (
|
||||
defaultValues?.map((defaultValue) => ({
|
||||
urn: defaultValue.urn,
|
||||
component: <TagTermLabel entity={defaultValue.entity} />,
|
||||
})) || []
|
||||
);
|
||||
};
|
||||
|
||||
export default function EditTagTermsModal({
|
||||
visible,
|
||||
onCloseModal,
|
||||
resources,
|
||||
type = EntityType.Tag,
|
||||
operationType = OperationType.ADD,
|
||||
defaultValues = [],
|
||||
onOkOverride,
|
||||
}: EditTagsModalProps) {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||
const [disableAction, setDisableAction] = useState(false);
|
||||
const [urns, setUrns] = useState<string[]>([]);
|
||||
const [selectedTerms, setSelectedTerms] = useState<any[]>([]);
|
||||
const [selectedTags, setSelectedTags] = useState<any[]>([]);
|
||||
const [urns, setUrns] = useState<string[]>(defaultValues.map((defaultValue) => defaultValue.urn));
|
||||
const [selectedTerms, setSelectedTerms] = useState<any[]>(
|
||||
type === EntityType.GlossaryTerm ? defaultValuesToSelectedValue(defaultValues) : [],
|
||||
);
|
||||
|
||||
const [selectedTags, setSelectedTags] = useState<any[]>(
|
||||
type === EntityType.Tag ? defaultValuesToSelectedValue(defaultValues) : [],
|
||||
);
|
||||
|
||||
const [isFocusedOnInput, setIsFocusedOnInput] = useState(false);
|
||||
|
||||
const [batchAddTagsMutation] = useBatchAddTagsMutation();
|
||||
@ -118,16 +134,7 @@ export default function EditTagTermsModal({
|
||||
const renderSearchResult = (entity: Entity) => {
|
||||
const displayName =
|
||||
entity.type === EntityType.Tag ? (entity as Tag).name : entityRegistry.getDisplayName(entity.type, entity);
|
||||
const tagOrTermComponent =
|
||||
entity.type === EntityType.Tag ? (
|
||||
<TagLabel
|
||||
name={displayName}
|
||||
colorHash={(entity as Tag).urn}
|
||||
color={(entity as Tag).properties?.colorHex}
|
||||
/>
|
||||
) : (
|
||||
<TermLabel name={displayName} />
|
||||
);
|
||||
const tagOrTermComponent = <TagTermLabel entity={entity} />;
|
||||
return (
|
||||
<Select.Option value={entity.urn} key={entity.urn} name={displayName}>
|
||||
{tagOrTermComponent}
|
||||
@ -209,18 +216,15 @@ export default function EditTagTermsModal({
|
||||
const selectedSearchOption = tagSearchOptions?.find((option) => option.props.value === urn);
|
||||
const selectedTagOption = tagResult?.find((tag) => tag.urn === urn);
|
||||
setUrns(newUrns);
|
||||
setSelectedTerms([...selectedTerms, { urn, component: <TermLabel name={selectedSearchOption?.props.name} /> }]);
|
||||
setSelectedTerms([
|
||||
...selectedTerms,
|
||||
{ urn, component: <TagTermLabel termName={selectedSearchOption?.props.name} /> },
|
||||
]);
|
||||
setSelectedTags([
|
||||
...selectedTags,
|
||||
{
|
||||
urn,
|
||||
component: (
|
||||
<TagLabel
|
||||
name={selectedSearchOption?.props.name}
|
||||
colorHash={(selectedTagOption as Tag).urn}
|
||||
color={(selectedTagOption as Tag).properties?.colorHex}
|
||||
/>
|
||||
),
|
||||
component: <TagTermLabel entity={selectedTagOption} />,
|
||||
},
|
||||
]);
|
||||
if (inputEl && inputEl.current) {
|
||||
@ -368,6 +372,11 @@ export default function EditTagTermsModal({
|
||||
|
||||
// Function to handle the modal action's
|
||||
const onOk = () => {
|
||||
if (onOkOverride) {
|
||||
onOkOverride(urns);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!resources) {
|
||||
onCloseModal();
|
||||
return;
|
||||
@ -385,7 +394,7 @@ export default function EditTagTermsModal({
|
||||
setIsFocusedOnInput(false);
|
||||
const newUrns = [...(urns || []), urn];
|
||||
setUrns(newUrns);
|
||||
setSelectedTerms([...selectedTerms, { urn, component: <TermLabel name={displayName} /> }]);
|
||||
setSelectedTerms([...selectedTerms, { urn, component: <TagTermLabel termName={displayName} /> }]);
|
||||
}
|
||||
|
||||
function clearInput() {
|
||||
|
||||
@ -371,7 +371,6 @@ export default function TagTermGroup({
|
||||
subResourceType: entitySubresource ? SubResourceType.DatasetField : null,
|
||||
},
|
||||
]}
|
||||
entityType={entityType}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
36
datahub-web-react/src/app/shared/tags/TagTermLabel.tsx
Normal file
36
datahub-web-react/src/app/shared/tags/TagTermLabel.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Entity, EntityType, Tag } from '../../../types.generated';
|
||||
import { useEntityRegistry } from '../../useEntityRegistry';
|
||||
import TagLabel from '../TagLabel';
|
||||
import TermLabel from '../TermLabel';
|
||||
|
||||
type Props = {
|
||||
// default behavior is to accept an entity and render label based on that
|
||||
entity?: Entity | null;
|
||||
|
||||
// if no entity is available, for terms just a name may be provided
|
||||
termName?: string;
|
||||
};
|
||||
|
||||
export const TagTermLabel = ({ entity, termName }: Props) => {
|
||||
const entityRegistry = useEntityRegistry();
|
||||
|
||||
if (entity?.type === EntityType.Tag) {
|
||||
return (
|
||||
<TagLabel
|
||||
name={entityRegistry.getDisplayName(entity.type, entity)}
|
||||
colorHash={(entity as Tag).urn}
|
||||
color={(entity as Tag).properties?.colorHex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (entity?.type === EntityType.GlossaryTerm) {
|
||||
return <TermLabel name={entityRegistry.getDisplayName(entity.type, entity)} />;
|
||||
}
|
||||
|
||||
if (termName) {
|
||||
return <TermLabel name={termName} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user