Skip to content

Commit

Permalink
feat(impact analysis): bugfixes for Impact Analysis (datahub-project#…
Browse files Browse the repository at this point in the history
  • Loading branch information
gabe-lyons authored Mar 7, 2022
1 parent a21e3c1 commit 2903646
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 104 deletions.
11 changes: 10 additions & 1 deletion datahub-web-react/src/app/analytics/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum EventType {
RecommendationClickEvent,
SearchAcrossLineageEvent,
SearchAcrossLineageResultsViewEvent,
DownloadAsCsvEvent,
}

/**
Expand Down Expand Up @@ -135,7 +136,6 @@ export const EntityActionType = {
UpdateSchemaTerms: 'UpdateSchemaTerms',
ClickExternalUrl: 'ClickExternalUrl',
};

export interface EntityActionEvent extends BaseEvent {
type: EventType.EntityActionEvent;
actionType: string;
Expand Down Expand Up @@ -176,6 +176,14 @@ export interface SearchAcrossLineageResultsViewEvent extends BaseEvent {
total: number;
}

export interface DownloadAsCsvEvent extends BaseEvent {
type: EventType.DownloadAsCsvEvent;
query: string;
// optional parameter if its coming from inside an entity page
entityUrn?: string;
path: string;
}

/**
* Event consisting of a union of specific event types.
*/
Expand All @@ -193,4 +201,5 @@ export type Event =
| RecommendationImpressionEvent
| SearchAcrossLineageEvent
| SearchAcrossLineageResultsViewEvent
| DownloadAsCsvEvent
| RecommendationClickEvent;
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import React, { useState } from 'react';
import { Button, Input, Modal } from 'antd';
import React from 'react';
import { Button } from 'antd';
import { DownloadOutlined } from '@ant-design/icons';
import styled from 'styled-components';
import { EntityType, FacetFilterInput, SearchAcrossEntitiesInput } from '../../../../../../types.generated';
import { SearchResultsInterface } from './types';
import { getSearchCsvDownloadHeader, transformResultsToCsvRow } from './downloadAsCsvUtil';
import { downloadRowsAsCsv } from '../../../../../search/utils/csvUtils';
import { useEntityRegistry } from '../../../../../useEntityRegistry';
import { useEntityData } from '../../../EntityContext';

const DownloadCsvButton = styled(Button)`
font-size: 12px;
Expand All @@ -16,94 +10,17 @@ const DownloadCsvButton = styled(Button)`
`;

type Props = {
callSearchOnVariables: (variables: {
input: SearchAcrossEntitiesInput;
}) => Promise<SearchResultsInterface | null | undefined>;
entityFilters: EntityType[];
filters: FacetFilterInput[];
query: string;
setShowDownloadAsCsvModal: (showDownloadAsCsvModal: boolean) => any;
isDownloadingCsv: boolean;
};

const SEARCH_PAGE_SIZE_FOR_DOWNLOAD = 1000;

export default function DownloadAsCsvButton({ callSearchOnVariables, entityFilters, filters, query }: Props) {
const { entityData: entitySearchIsEmbeddedWithin } = useEntityData();

const [isDownloadingCsv, setIsDownloadingCsv] = useState(false);
const [showSaveAsModal, setShowSaveAsModal] = useState(false);
const [saveAsTitle, setSaveAsTitle] = useState(
entitySearchIsEmbeddedWithin ? `${entitySearchIsEmbeddedWithin.name}_impact.csv` : 'results.csv',
);
const entityRegistry = useEntityRegistry();

const triggerCsvDownload = (filename) => {
setIsDownloadingCsv(true);
console.log('preparing your csv');

let downloadPage = 0;
let accumulatedResults: string[][] = [];

function fetchNextPage() {
console.log('fetch page number ', downloadPage);
callSearchOnVariables({
input: {
types: entityFilters,
query,
start: SEARCH_PAGE_SIZE_FOR_DOWNLOAD * downloadPage,
count: SEARCH_PAGE_SIZE_FOR_DOWNLOAD,
filters,
},
}).then((refetchData) => {
console.log('fetched data for page number ', downloadPage);
accumulatedResults = [
...accumulatedResults,
...transformResultsToCsvRow(refetchData?.searchResults || [], entityRegistry),
];
if ((refetchData?.start || 0) + (refetchData?.count || 0) < (refetchData?.total || 0)) {
downloadPage += 1;
fetchNextPage();
} else {
setIsDownloadingCsv(false);
downloadRowsAsCsv(
getSearchCsvDownloadHeader(refetchData?.searchResults[0]),
accumulatedResults,
filename,
);
}
});
}

fetchNextPage();
};

export default function DownloadAsCsvButton({ setShowDownloadAsCsvModal, isDownloadingCsv }: Props) {
return (
<>
<DownloadCsvButton type="text" onClick={() => setShowSaveAsModal(true)} disabled={isDownloadingCsv}>
<DownloadCsvButton type="text" onClick={() => setShowDownloadAsCsvModal(true)} disabled={isDownloadingCsv}>
<DownloadOutlined />
{isDownloadingCsv ? 'Downloading...' : 'Download'}
</DownloadCsvButton>
<Modal
title="Download as..."
visible={showSaveAsModal}
footer={
<>
<Button onClick={() => setShowSaveAsModal(false)} type="text">
Close
</Button>
<Button
onClick={() => {
setShowSaveAsModal(false);
triggerCsvDownload(saveAsTitle);
}}
disabled={saveAsTitle.length === 0}
>
Download
</Button>
</>
}
>
<Input placeholder="datahub.csv" value={saveAsTitle} onChange={(e) => setSaveAsTitle(e.target.value)} />
</Modal>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React, { useState } from 'react';
import { Button, Input, Modal } from 'antd';
import { useLocation } from 'react-router';

import { EntityType, FacetFilterInput, SearchAcrossEntitiesInput } from '../../../../../../types.generated';
import { SearchResultsInterface } from './types';
import { getSearchCsvDownloadHeader, transformResultsToCsvRow } from './downloadAsCsvUtil';
import { downloadRowsAsCsv } from '../../../../../search/utils/csvUtils';
import { useEntityRegistry } from '../../../../../useEntityRegistry';
import { useEntityData } from '../../../EntityContext';
import analytics, { EventType } from '../../../../../analytics';

type Props = {
callSearchOnVariables: (variables: {
input: SearchAcrossEntitiesInput;
}) => Promise<SearchResultsInterface | null | undefined>;
entityFilters: EntityType[];
filters: FacetFilterInput[];
query: string;
setIsDownloadingCsv: (isDownloadingCsv: boolean) => any;
showDownloadAsCsvModal: boolean;
setShowDownloadAsCsvModal: (showDownloadAsCsvModal: boolean) => any;
};

const SEARCH_PAGE_SIZE_FOR_DOWNLOAD = 1000;

export default function DownloadAsCsvModal({
callSearchOnVariables,
entityFilters,
filters,
query,
setIsDownloadingCsv,
showDownloadAsCsvModal,
setShowDownloadAsCsvModal,
}: Props) {
const { entityData: entitySearchIsEmbeddedWithin } = useEntityData();
const location = useLocation();

const [saveAsTitle, setSaveAsTitle] = useState(
entitySearchIsEmbeddedWithin ? `${entitySearchIsEmbeddedWithin.name}_impact.csv` : 'results.csv',
);
const entityRegistry = useEntityRegistry();

const triggerCsvDownload = (filename) => {
setIsDownloadingCsv(true);
console.log('preparing your csv');

let downloadPage = 0;
let accumulatedResults: string[][] = [];

analytics.event({
type: EventType.DownloadAsCsvEvent,
query,
entityUrn: entitySearchIsEmbeddedWithin?.urn,
path: location.pathname,
});

function fetchNextPage() {
console.log('fetch page number ', downloadPage);
callSearchOnVariables({
input: {
types: entityFilters,
query,
start: SEARCH_PAGE_SIZE_FOR_DOWNLOAD * downloadPage,
count: SEARCH_PAGE_SIZE_FOR_DOWNLOAD,
filters,
},
}).then((refetchData) => {
console.log('fetched data for page number ', downloadPage);
accumulatedResults = [
...accumulatedResults,
...transformResultsToCsvRow(refetchData?.searchResults || [], entityRegistry),
];
if ((refetchData?.start || 0) + (refetchData?.count || 0) < (refetchData?.total || 0)) {
downloadPage += 1;
fetchNextPage();
} else {
setIsDownloadingCsv(false);
downloadRowsAsCsv(
getSearchCsvDownloadHeader(refetchData?.searchResults[0]),
accumulatedResults,
filename,
);
}
});
}

fetchNextPage();
};

return (
<Modal
centered
onCancel={() => setShowDownloadAsCsvModal(false)}
title="Download as..."
visible={showDownloadAsCsvModal}
footer={
<>
<Button onClick={() => setShowDownloadAsCsvModal(false)} type="text">
Close
</Button>
<Button
onClick={() => {
setShowDownloadAsCsvModal(false);
triggerCsvDownload(saveAsTitle);
}}
disabled={saveAsTitle.length === 0}
>
Download
</Button>
</>
}
>
<Input
placeholder="datahub.csv"
value={saveAsTitle}
onChange={(e) => {
setSaveAsTitle(e.target.value);
}}
/>
</Modal>
);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import React, { useState } from 'react';
import { Dropdown, Menu } from 'antd';
import { MoreOutlined } from '@ant-design/icons';
import styled from 'styled-components';
import { EntityType, FacetFilterInput, SearchAcrossEntitiesInput } from '../../../../../../types.generated';
import { SearchResultsInterface } from './types';
import DownloadAsCsvButton from './DownloadAsCsvButton';
import DownloadAsCsvModal from './DownloadAsCsvModal';

const MenuIcon = styled(MoreOutlined)`
font-size: 15px;
Expand All @@ -22,22 +23,34 @@ type Props = {

// currently only contains Download As Csv but will be extended to contain other actions as well
export default function SearchExtendedMenu({ callSearchOnVariables, entityFilters, filters, query }: Props) {
const [isDownloadingCsv, setIsDownloadingCsv] = useState(false);
const [showDownloadAsCsvModal, setShowDownloadAsCsvModal] = useState(false);

const menu = (
<Menu>
<Menu.Item key="0">
<DownloadAsCsvButton
callSearchOnVariables={callSearchOnVariables}
entityFilters={entityFilters}
filters={filters}
query={query}
isDownloadingCsv={isDownloadingCsv}
setShowDownloadAsCsvModal={setShowDownloadAsCsvModal}
/>
</Menu.Item>
</Menu>
);

return (
<Dropdown overlay={menu} trigger={['click']}>
<MenuIcon />
</Dropdown>
<>
<DownloadAsCsvModal
callSearchOnVariables={callSearchOnVariables}
entityFilters={entityFilters}
filters={filters}
query={query}
setIsDownloadingCsv={setIsDownloadingCsv}
showDownloadAsCsvModal={showDownloadAsCsvModal}
setShowDownloadAsCsvModal={setShowDownloadAsCsvModal}
/>
<Dropdown overlay={menu} trigger={['click']}>
<MenuIcon />
</Dropdown>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ export const transformGenericEntityPropertiesToCsvRow = (
// user owners
properties?.ownership?.owners
?.filter((owner) => owner.owner.type === EntityType.CorpUser)
.map((owner) => (owner.owner as CorpUser).properties?.fullName)
.map(
(owner) =>
(owner.owner as CorpUser).editableProperties?.displayName ||
(owner.owner as CorpUser).properties?.fullName ||
(owner.owner as CorpUser).properties?.displayName,
)
.join(',') || '',
// user owner emails
properties?.ownership?.owners
Expand All @@ -66,9 +71,7 @@ export const transformGenericEntityPropertiesToCsvRow = (
// group owner emails
properties?.ownership?.owners
?.filter((owner) => owner.owner.type === EntityType.CorpGroup)
.map(
(owner) => (owner.owner as CorpGroup).properties?.email || (owner.owner as CorpGroup).properties?.email,
)
.map((owner) => (owner.owner as CorpGroup).properties?.email)
.join(',') || '',
// tags
properties?.globalTags?.tags?.map((tag) => tag.tag.name).join(',') || '',
Expand Down
4 changes: 2 additions & 2 deletions datahub-web-react/src/app/search/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ const FiltersHeader = styled.div`
padding-bottom: 8px;
width: 100%;
height: 46px;
line-height: 46px;
height: 47px;
line-height: 47px;
border-bottom: 1px solid;
border-color: ${(props) => props.theme.styles['border-color-base']};
`;
Expand Down
14 changes: 14 additions & 0 deletions datahub-web-react/src/graphql/fragments.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,30 @@ fragment ownershipFields on Ownership {
lastName
fullName
}
properties {
active
displayName
title
email
firstName
lastName
fullName
}
editableProperties {
displayName
title
pictureLink
email
}
}
... on CorpGroup {
urn
type
name
properties {
displayName
email
}
info {
displayName
email
Expand Down
Loading

0 comments on commit 2903646

Please sign in to comment.