feat(embed): embed lookup route (#8033)

This commit is contained in:
Joshua Eilers 2023-05-19 11:42:46 -07:00 committed by GitHub
parent 72bcfc53af
commit d3dc2f22a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 207 additions and 3 deletions

View File

@ -7,6 +7,7 @@ import { PageRoutes } from '../conf/Global';
import EmbeddedPage from './embed/EmbeddedPage';
import { useEntityRegistry } from './useEntityRegistry';
import AppProviders from './AppProviders';
import EmbedLookup from './embed/lookup';
/**
* Container for all views behind an authentication wall.
@ -20,6 +21,7 @@ export const ProtectedRoutes = (): JSX.Element => {
<Layout>
<Switch>
<Route exact path="/" render={() => <HomePage />} />
<Route exact path={PageRoutes.EMBED_LOOKUP} render={() => <EmbedLookup />} />
{entityRegistry.getEntities().map((entity) => (
<Route
key={`${entity.getPathName()}/${PageRoutes.EMBED}`}

View File

@ -1,4 +1,5 @@
import { DataHubViewType, EntityType, RecommendationRenderType, ScenarioType } from '../../types.generated';
import { EmbedLookupNotFoundReason } from '../embed/lookup/constants';
import { Direction } from '../lineage/types';
/**
@ -68,6 +69,7 @@ export enum EventType {
DeselectQuickFilterEvent,
EmbedProfileViewEvent,
EmbedProfileViewInDataHubEvent,
EmbedLookupNotFoundEvent,
}
/**
@ -519,6 +521,12 @@ export interface EmbedProfileViewInDataHubEvent extends BaseEvent {
entityUrn: string;
}
export interface EmbedLookupNotFoundEvent extends BaseEvent {
type: EventType.EmbedLookupNotFoundEvent;
url: string;
reason: EmbedLookupNotFoundReason;
}
/**
* Event consisting of a union of specific event types.
*/
@ -585,4 +593,5 @@ export type Event =
| SelectQuickFilterEvent
| DeselectQuickFilterEvent
| EmbedProfileViewEvent
| EmbedProfileViewInDataHubEvent;
| EmbedProfileViewInDataHubEvent
| EmbedLookupNotFoundEvent;

View File

@ -0,0 +1,38 @@
import React from 'react';
import { useParams } from 'react-router';
import styled from 'styled-components';
import { ErrorSection } from '../../shared/error/ErrorSection';
import useGetEntityByUrl from './useGetEntityByUrl';
import LookupNotFound from './LookupNotFound';
import LookupFoundMultiple from './LookupFoundMultiple';
import LookupLoading from './LookupLoading';
import GoToLookup from './GoToLookup';
type RouteParams = {
url: string;
};
const PageContainer = styled.div`
display: flex;
align-items: center;
justify-content: center;
height: 85vh;
`;
const EmbedLookup = () => {
const { url: encodedUrl } = useParams<RouteParams>();
const decodedUrl = decodeURIComponent(encodedUrl);
const { count, entity, error, loading } = useGetEntityByUrl(decodedUrl);
const getContent = () => {
if (loading) return <LookupLoading />;
if (error) return <ErrorSection />;
if (count === 0 || !entity) return <LookupNotFound url={encodedUrl} />;
if (count > 1) return <LookupFoundMultiple url={encodedUrl} />;
return <GoToLookup entityType={entity.type} entityUrn={entity.urn} />;
};
return <PageContainer>{getContent()}</PageContainer>;
};
export default EmbedLookup;

View File

@ -0,0 +1,19 @@
import React, { useEffect } from 'react';
import { useHistory } from 'react-router';
import { EntityType } from '../../../types.generated';
import { useEntityRegistry } from '../../useEntityRegistry';
import { PageRoutes } from '../../../conf/Global';
import { urlEncodeUrn } from '../../entity/shared/utils';
import LookupLoading from './LookupLoading';
const GoToLookup = ({ entityType, entityUrn }: { entityType: EntityType; entityUrn: string }) => {
const registry = useEntityRegistry();
const history = useHistory();
useEffect(() => {
const entityUrl = `${PageRoutes.EMBED}/${registry.getPathName(entityType)}/${urlEncodeUrn(entityUrn)}`;
history.push(entityUrl);
}, [entityType, entityUrn, history, registry]);
return <LookupLoading />;
};
export default GoToLookup;

View File

@ -0,0 +1,13 @@
import React, { useEffect } from 'react';
import useEmbedLookupAnalytics from './useEmbedAnalytics';
import NonExistentEntityPage from '../../entity/shared/entity/NonExistentEntityPage';
const LookupFoundMultiple = ({ url }: { url: string }) => {
const { trackLookupMultipleFoundEvent } = useEmbedLookupAnalytics();
useEffect(() => {
trackLookupMultipleFoundEvent(url);
}, [trackLookupMultipleFoundEvent, url]);
return <NonExistentEntityPage />;
};
export default LookupFoundMultiple;

View File

@ -0,0 +1,8 @@
import { LoadingOutlined } from '@ant-design/icons';
import styled from 'styled-components';
const LookupLoading = styled(LoadingOutlined)`
font-size: 50px;
`;
export default LookupLoading;

View File

@ -0,0 +1,13 @@
import React, { useEffect } from 'react';
import useEmbedLookupAnalytics from './useEmbedAnalytics';
import NonExistentEntityPage from '../../entity/shared/entity/NonExistentEntityPage';
const LookupNotFound = ({ url }: { url: string }) => {
const { trackLookupNotFoundEvent } = useEmbedLookupAnalytics();
useEffect(() => {
trackLookupNotFoundEvent(url);
}, [trackLookupNotFoundEvent, url]);
return <NonExistentEntityPage />;
};
export default LookupNotFound;

View File

@ -0,0 +1,7 @@
export const EMBED_LOOKUP_NOT_FOUND_REASON = {
NO_ENTITY_FOUND: 'NO_ENTITY_FOUND',
MULTIPLE_ENTITIES_FOUND: 'MULTIPLE_ENTITIES_FOUND',
} as const;
export type EmbedLookupNotFoundReason =
typeof EMBED_LOOKUP_NOT_FOUND_REASON[keyof typeof EMBED_LOOKUP_NOT_FOUND_REASON];

View File

@ -0,0 +1,3 @@
import EmbedLookup from './EmbedLookup';
export default EmbedLookup;

View File

@ -0,0 +1,26 @@
import { useCallback } from 'react';
import analytics from '../../analytics/analytics';
import { EventType } from '../../analytics';
import { EMBED_LOOKUP_NOT_FOUND_REASON } from './constants';
const useEmbedLookupAnalytics = () => {
const trackLookupNotFoundEvent = useCallback((url: string) => {
analytics.event({
type: EventType.EmbedLookupNotFoundEvent,
url,
reason: EMBED_LOOKUP_NOT_FOUND_REASON.NO_ENTITY_FOUND,
});
}, []);
const trackLookupMultipleFoundEvent = useCallback((url: string) => {
analytics.event({
type: EventType.EmbedLookupNotFoundEvent,
url,
reason: EMBED_LOOKUP_NOT_FOUND_REASON.MULTIPLE_ENTITIES_FOUND,
});
}, []);
return { trackLookupNotFoundEvent, trackLookupMultipleFoundEvent } as const;
};
export default useEmbedLookupAnalytics;

View File

@ -0,0 +1,44 @@
import { useMemo } from 'react';
import { useGetSearchResultsForMultipleQuery } from '../../../graphql/search.generated';
import { FilterOperator } from '../../../types.generated';
import { UnionType } from '../../search/utils/constants';
import { generateOrFilters } from '../../search/utils/generateOrFilters';
const URL_FIELDS = ['externalUrl', 'chartUrl', 'dashboardUrl'] as const;
const useGetEntityByUrl = (externalUrl: string) => {
const { data, loading, error } = useGetSearchResultsForMultipleQuery({
variables: {
input: {
query: '*',
start: 0,
count: 2,
orFilters: generateOrFilters(
UnionType.OR,
URL_FIELDS.map((field) => ({
field,
values: [externalUrl],
condition: FilterOperator.Equal,
})),
),
},
},
});
const entities = data?.searchAcrossEntities?.searchResults.map((result) => result.entity) ?? [];
const count = entities.length;
const entity = count === 1 ? entities[0] : null;
return useMemo(
() =>
({
count,
entity,
loading,
error,
} as const),
[count, entity, error, loading],
);
};
export default useGetEntityByUrl;

View File

@ -24,6 +24,7 @@ import { EmbedTab } from '../shared/tabs/Embed/EmbedTab';
import { capitalizeFirstLetterOnly } from '../../shared/textUtil';
import DataProductSection from '../shared/containers/profile/sidebar/DataProduct/DataProductSection';
import { getDataProduct } from '../shared/utils';
import EmbeddedProfile from '../shared/embed/EmbeddedProfile';
/**
* Definition of the DataHub Chart entity.
@ -237,4 +238,13 @@ export class ChartEntity implements Entity<Chart> {
EntityCapabilityType.DATA_PRODUCTS,
]);
};
renderEmbeddedProfile = (urn: string) => (
<EmbeddedProfile
urn={urn}
entityType={EntityType.Chart}
useEntityQuery={useGetChartQuery}
getOverrideProperties={this.getOverridePropertiesFromEntity}
/>
);
}

View File

@ -16,6 +16,7 @@ import { SidebarDomainSection } from '../shared/containers/profile/sidebar/Domai
import { capitalizeFirstLetterOnly } from '../../shared/textUtil';
import DataProductSection from '../shared/containers/profile/sidebar/DataProduct/DataProductSection';
import { getDataProduct } from '../shared/utils';
import EmbeddedProfile from '../shared/embed/EmbeddedProfile';
/**
* Definition of the DataHub Container entity.
@ -186,4 +187,13 @@ export class ContainerEntity implements Entity<Container> {
EntityCapabilityType.DATA_PRODUCTS,
]);
};
renderEmbeddedProfile = (urn: string) => (
<EmbeddedProfile
urn={urn}
entityType={EntityType.Container}
useEntityQuery={useGetContainerQuery}
getOverrideProperties={this.getOverridePropertiesFromEntity}
/>
);
}

View File

@ -78,7 +78,7 @@ export const ErrorSection = (): JSX.Element => {
</DetailParagraph>
<ResourceList>
{resources.map((resource) => (
<ResourceListItem>
<ResourceListItem key={resource.path}>
<a href={resource.path}>{resource.label}</a>
</ResourceListItem>
))}

View File

@ -27,6 +27,7 @@ export enum PageRoutes {
GLOSSARY = '/glossary',
SETTINGS_VIEWS = '/settings/views',
EMBED = '/embed',
EMBED_LOOKUP = '/embed/lookup/:url',
}
/**

View File

@ -67,7 +67,8 @@ public enum DataHubUsageEventType {
SELECT_QUICK_FILTER_EVENT("SelectQuickFilterEvent"),
DESELECT_QUICK_FILTER_EVENT("DeselectQuickFilterEvent"),
EMBED_PROFILE_VIEW_EVENT("EmbedProfileViewEvent"),
EMBED_PROFILE_VIEW_IN_DATAHUB_EVENT("EmbedProfileViewInDataHubEvent");
EMBED_PROFILE_VIEW_IN_DATAHUB_EVENT("EmbedProfileViewInDataHubEvent"),
EMBED_LOOKUP_NOT_FOUND_EVENT("EmbedLookupNotFoundEvent");
private final String type;