mirror of
https://github.com/datahub-project/datahub.git
synced 2025-09-01 05:13:15 +00:00
feat(data platform) adding data platform indexing & select platform modal in frontend (#5988)
* adding data platform indexing & select platform modal in frontend * comments
This commit is contained in:
parent
a432f495a9
commit
b9b0147dd1
@ -0,0 +1,186 @@
|
|||||||
|
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 { DataPlatform, Entity, EntityType } from '../../../../../../../types.generated';
|
||||||
|
import { useEnterKeyListener } from '../../../../../../shared/useEnterKeyListener';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onCloseModal: () => void;
|
||||||
|
defaultValues?: { urn: string; entity?: Entity | null }[];
|
||||||
|
onOkOverride?: (result: string[]) => void;
|
||||||
|
titleOverride?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SelectedPlatform = {
|
||||||
|
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 SelectPlatformModal = ({ onCloseModal, defaultValues, onOkOverride, titleOverride }: Props) => {
|
||||||
|
const [platformSearch, { data: platforSearchData }] = useGetSearchResultsLazyQuery();
|
||||||
|
const platformSearchResults =
|
||||||
|
platforSearchData?.search?.searchResults?.map((searchResult) => searchResult.entity) || [];
|
||||||
|
|
||||||
|
const [selectedPlatforms, setSelectedPlatforms] = useState<SelectedPlatform[] | undefined>(defaultValues);
|
||||||
|
|
||||||
|
const inputEl = useRef(null);
|
||||||
|
|
||||||
|
const onModalClose = () => {
|
||||||
|
onCloseModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = (text: string) => {
|
||||||
|
platformSearch({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
type: EntityType.DataPlatform,
|
||||||
|
query: text,
|
||||||
|
start: 0,
|
||||||
|
count: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Renders a search result in the select dropdown.
|
||||||
|
const renderSearchResult = (entity: DataPlatform) => {
|
||||||
|
const displayName = entity.properties?.displayName || entity.name;
|
||||||
|
const truncatedDisplayName = displayName.length > 25 ? `${displayName.slice(0, 25)}...` : displayName;
|
||||||
|
return (
|
||||||
|
<Tooltip title={displayName}>
|
||||||
|
{!!entity.properties?.logoUrl && <PreviewImage src={entity?.properties?.logoUrl} alt={entity?.name} />}
|
||||||
|
<span>{truncatedDisplayName}</span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const platformSearchOptions = platformSearchResults?.map((result) => {
|
||||||
|
return (
|
||||||
|
<Select.Option value={result.urn} key={result.urn}>
|
||||||
|
{renderSearchResult(result as DataPlatform)}
|
||||||
|
</Select.Option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSelectPlatform = (newValue: { value: string; label: ReactNode }) => {
|
||||||
|
const newUrn = newValue.value;
|
||||||
|
|
||||||
|
if (inputEl && inputEl.current) {
|
||||||
|
(inputEl.current as any).blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredPlatforms =
|
||||||
|
platformSearchResults?.filter((entity) => entity.urn === newUrn).map((entity) => entity) || [];
|
||||||
|
|
||||||
|
if (filteredPlatforms.length) {
|
||||||
|
const platform = filteredPlatforms[0] as DataPlatform;
|
||||||
|
setSelectedPlatforms([
|
||||||
|
...(selectedPlatforms || []),
|
||||||
|
{
|
||||||
|
entity: platform,
|
||||||
|
urn: newUrn,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeselectPlatform = (val) => {
|
||||||
|
setSelectedPlatforms(selectedPlatforms?.filter((platform) => platform.urn !== val));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
if (!selectedPlatforms) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onOkOverride) {
|
||||||
|
onOkOverride(selectedPlatforms?.map((platform) => platform.urn));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle the Enter press
|
||||||
|
useEnterKeyListener({
|
||||||
|
querySelectorToExecuteClick: '#setPlatformButton',
|
||||||
|
});
|
||||||
|
|
||||||
|
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 Platform'}
|
||||||
|
visible
|
||||||
|
onCancel={onModalClose}
|
||||||
|
footer={
|
||||||
|
<>
|
||||||
|
<Button onClick={onModalClose} type="text">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button id="setPlatformButton" disabled={selectedPlatforms?.length === 0} onClick={onOk}>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Form component={false}>
|
||||||
|
<Form.Item>
|
||||||
|
<Select
|
||||||
|
autoFocus
|
||||||
|
filterOption={false}
|
||||||
|
showSearch
|
||||||
|
mode="multiple"
|
||||||
|
defaultActiveFirstOption={false}
|
||||||
|
placeholder="Search for Platforms..."
|
||||||
|
onSelect={(platformUrn: any) => onSelectPlatform(platformUrn)}
|
||||||
|
onDeselect={onDeselectPlatform}
|
||||||
|
onSearch={(value: string) => {
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
handleSearch(value.trim());
|
||||||
|
}}
|
||||||
|
ref={inputEl}
|
||||||
|
labelInValue
|
||||||
|
value={selectedPlatforms?.map((platform) => ({
|
||||||
|
value: platform.urn,
|
||||||
|
label: platform.entity ? (
|
||||||
|
renderSearchResult(platform.entity as DataPlatform)
|
||||||
|
) : (
|
||||||
|
<span>{platform.urn}</span>
|
||||||
|
),
|
||||||
|
}))}
|
||||||
|
tagRender={tagRender}
|
||||||
|
>
|
||||||
|
{platformSearchOptions}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
@ -162,6 +162,9 @@ fragment autoCompleteFields on Entity {
|
|||||||
...dataPlatformInstanceFields
|
...dataPlatformInstanceFields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
... on DataPlatform {
|
||||||
|
...nonConflictingPlatformFields
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query getAutoCompleteResults($input: AutoCompleteInput!) {
|
query getAutoCompleteResults($input: AutoCompleteInput!) {
|
||||||
@ -672,6 +675,9 @@ fragment searchResultFields on Entity {
|
|||||||
}
|
}
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
|
... on DataPlatform {
|
||||||
|
...nonConflictingPlatformFields
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment facetFields on FacetMetadata {
|
fragment facetFields on FacetMetadata {
|
||||||
|
@ -14,11 +14,21 @@ record DataPlatformInfo {
|
|||||||
* Name of the data platform
|
* Name of the data platform
|
||||||
*/
|
*/
|
||||||
@validate.strlen.max = 15
|
@validate.strlen.max = 15
|
||||||
|
@Searchable = {
|
||||||
|
"fieldType": "TEXT_PARTIAL",
|
||||||
|
"enableAutocomplete": false,
|
||||||
|
"boostScore": 10.0
|
||||||
|
}
|
||||||
name: string
|
name: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name that will be used for displaying a platform type.
|
* The name that will be used for displaying a platform type.
|
||||||
*/
|
*/
|
||||||
|
@Searchable = {
|
||||||
|
"fieldType": "TEXT_PARTIAL",
|
||||||
|
"enableAutocomplete": true,
|
||||||
|
"boostScore": 10.0
|
||||||
|
}
|
||||||
displayName: optional string
|
displayName: optional string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,4 +95,4 @@ record DataPlatformInfo {
|
|||||||
* The URL for a logo associated with the platform
|
* The URL for a logo associated with the platform
|
||||||
*/
|
*/
|
||||||
logoUrl: optional Url
|
logoUrl: optional Url
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import com.linkedin.gms.factory.search.EntitySearchServiceFactory;
|
|||||||
import com.linkedin.gms.factory.search.SearchDocumentTransformerFactory;
|
import com.linkedin.gms.factory.search.SearchDocumentTransformerFactory;
|
||||||
import com.linkedin.metadata.boot.BootstrapManager;
|
import com.linkedin.metadata.boot.BootstrapManager;
|
||||||
import com.linkedin.metadata.boot.BootstrapStep;
|
import com.linkedin.metadata.boot.BootstrapStep;
|
||||||
|
import com.linkedin.metadata.boot.steps.IndexDataPlatformsStep;
|
||||||
import com.linkedin.metadata.boot.steps.IngestDataPlatformInstancesStep;
|
import com.linkedin.metadata.boot.steps.IngestDataPlatformInstancesStep;
|
||||||
import com.linkedin.metadata.boot.steps.IngestDataPlatformsStep;
|
import com.linkedin.metadata.boot.steps.IngestDataPlatformsStep;
|
||||||
import com.linkedin.metadata.boot.steps.IngestPoliciesStep;
|
import com.linkedin.metadata.boot.steps.IngestPoliciesStep;
|
||||||
@ -79,13 +80,15 @@ public class BootstrapManagerFactory {
|
|||||||
new IngestDataPlatformInstancesStep(_entityService, _migrationsDao);
|
new IngestDataPlatformInstancesStep(_entityService, _migrationsDao);
|
||||||
final RestoreGlossaryIndices restoreGlossaryIndicesStep =
|
final RestoreGlossaryIndices restoreGlossaryIndicesStep =
|
||||||
new RestoreGlossaryIndices(_entityService, _entitySearchService, _entityRegistry);
|
new RestoreGlossaryIndices(_entityService, _entitySearchService, _entityRegistry);
|
||||||
|
final IndexDataPlatformsStep indexDataPlatformsStep =
|
||||||
|
new IndexDataPlatformsStep(_entityService, _entitySearchService, _entityRegistry);
|
||||||
final RestoreDbtSiblingsIndices restoreDbtSiblingsIndices =
|
final RestoreDbtSiblingsIndices restoreDbtSiblingsIndices =
|
||||||
new RestoreDbtSiblingsIndices(_entityService, _entityRegistry);
|
new RestoreDbtSiblingsIndices(_entityService, _entityRegistry);
|
||||||
final RemoveClientIdAspectStep removeClientIdAspectStep = new RemoveClientIdAspectStep(_entityService);
|
final RemoveClientIdAspectStep removeClientIdAspectStep = new RemoveClientIdAspectStep(_entityService);
|
||||||
|
|
||||||
final List<BootstrapStep> finalSteps = new ArrayList<>(ImmutableList.of(ingestRootUserStep, ingestPoliciesStep, ingestRolesStep,
|
final List<BootstrapStep> finalSteps = new ArrayList<>(ImmutableList.of(ingestRootUserStep, ingestPoliciesStep, ingestRolesStep,
|
||||||
ingestDataPlatformsStep, ingestDataPlatformInstancesStep, _ingestRetentionPoliciesStep, restoreGlossaryIndicesStep,
|
ingestDataPlatformsStep, ingestDataPlatformInstancesStep, _ingestRetentionPoliciesStep, restoreGlossaryIndicesStep,
|
||||||
removeClientIdAspectStep, restoreDbtSiblingsIndices));
|
removeClientIdAspectStep, restoreDbtSiblingsIndices, indexDataPlatformsStep));
|
||||||
|
|
||||||
if (_upgradeDefaultBrowsePathsEnabled) {
|
if (_upgradeDefaultBrowsePathsEnabled) {
|
||||||
finalSteps.add(new UpgradeDefaultBrowsePathsStep(_entityService));
|
finalSteps.add(new UpgradeDefaultBrowsePathsStep(_entityService));
|
||||||
|
@ -0,0 +1,113 @@
|
|||||||
|
package com.linkedin.metadata.boot.steps;
|
||||||
|
|
||||||
|
import com.linkedin.common.AuditStamp;
|
||||||
|
import com.linkedin.common.urn.Urn;
|
||||||
|
import com.linkedin.dataplatform.DataPlatformInfo;
|
||||||
|
import com.linkedin.entity.EntityResponse;
|
||||||
|
import com.linkedin.entity.EnvelopedAspectMap;
|
||||||
|
import com.linkedin.events.metadata.ChangeType;
|
||||||
|
import com.linkedin.metadata.Constants;
|
||||||
|
import com.linkedin.metadata.boot.UpgradeStep;
|
||||||
|
import com.linkedin.metadata.entity.EntityService;
|
||||||
|
import com.linkedin.metadata.models.AspectSpec;
|
||||||
|
import com.linkedin.metadata.models.registry.EntityRegistry;
|
||||||
|
import com.linkedin.metadata.query.ListUrnsResult;
|
||||||
|
import com.linkedin.metadata.search.EntitySearchService;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class IndexDataPlatformsStep extends UpgradeStep {
|
||||||
|
private static final String VERSION = "1";
|
||||||
|
private static final String UPGRADE_ID = "index-data-platforms";
|
||||||
|
private static final Integer BATCH_SIZE = 1000;
|
||||||
|
|
||||||
|
private final EntitySearchService _entitySearchService;
|
||||||
|
private final EntityRegistry _entityRegistry;
|
||||||
|
|
||||||
|
public IndexDataPlatformsStep(EntityService entityService, EntitySearchService entitySearchService,
|
||||||
|
EntityRegistry entityRegistry) {
|
||||||
|
super(entityService, VERSION, UPGRADE_ID);
|
||||||
|
_entitySearchService = entitySearchService;
|
||||||
|
_entityRegistry = entityRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void upgrade() throws Exception {
|
||||||
|
final AspectSpec dataPlatformSpec = _entityRegistry.getEntitySpec(Constants.DATA_PLATFORM_ENTITY_NAME)
|
||||||
|
.getAspectSpec(Constants.DATA_PLATFORM_INFO_ASPECT_NAME);
|
||||||
|
|
||||||
|
final AuditStamp auditStamp =
|
||||||
|
new AuditStamp().setActor(Urn.createFromString(Constants.SYSTEM_ACTOR)).setTime(System.currentTimeMillis());
|
||||||
|
|
||||||
|
getAndReIndexDataPlatforms(auditStamp, dataPlatformSpec);
|
||||||
|
|
||||||
|
log.info("Successfully indexed data platform aspects");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public ExecutionMode getExecutionMode() {
|
||||||
|
return ExecutionMode.ASYNC;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getAndReIndexDataPlatforms(AuditStamp auditStamp, AspectSpec dataPlatformInfoAspectSpec)
|
||||||
|
throws Exception {
|
||||||
|
ListUrnsResult listResult =
|
||||||
|
_entityService.listUrns(Constants.DATA_PLATFORM_ENTITY_NAME, 0, BATCH_SIZE);
|
||||||
|
|
||||||
|
List<Urn> dataPlatformUrns = listResult.getEntities();
|
||||||
|
|
||||||
|
if (dataPlatformUrns.size() == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<Urn, EntityResponse> dataPlatformInfoResponses =
|
||||||
|
_entityService.getEntitiesV2(Constants.DATA_PLATFORM_ENTITY_NAME, new HashSet<>(dataPlatformUrns),
|
||||||
|
Collections.singleton(Constants.DATA_PLATFORM_INFO_ASPECT_NAME)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Loop over Data platforms and produce changelog
|
||||||
|
for (Urn dpUrn : dataPlatformUrns) {
|
||||||
|
EntityResponse dataPlatformEntityResponse = dataPlatformInfoResponses.get(dpUrn);
|
||||||
|
if (dataPlatformEntityResponse == null) {
|
||||||
|
log.warn("Data Platform not in set of entity responses {}", dpUrn);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataPlatformInfo dpInfo = mapDpInfo(dataPlatformEntityResponse);
|
||||||
|
if (dpInfo == null) {
|
||||||
|
log.warn("Received null dataPlatformInfo aspect for urn {}", dpUrn);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_entityService.produceMetadataChangeLog(
|
||||||
|
dpUrn,
|
||||||
|
Constants.DATA_PLATFORM_ENTITY_NAME,
|
||||||
|
Constants.DATA_PLATFORM_INFO_ASPECT_NAME,
|
||||||
|
dataPlatformInfoAspectSpec,
|
||||||
|
null,
|
||||||
|
dpInfo,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
auditStamp,
|
||||||
|
ChangeType.RESTATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return listResult.getTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataPlatformInfo mapDpInfo(EntityResponse entityResponse) {
|
||||||
|
EnvelopedAspectMap aspectMap = entityResponse.getAspects();
|
||||||
|
if (!aspectMap.containsKey(Constants.DATA_PLATFORM_INFO_ASPECT_NAME)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DataPlatformInfo(aspectMap.get(Constants.DATA_PLATFORM_INFO_ASPECT_NAME).getValue().data());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user