diff --git a/datahub-web-react/src/Mocks.tsx b/datahub-web-react/src/Mocks.tsx index bb4007aea8..ef3f66d16e 100644 --- a/datahub-web-react/src/Mocks.tsx +++ b/datahub-web-react/src/Mocks.tsx @@ -477,6 +477,7 @@ export const mocks = [ total: 1, entities: [ { + __typename: 'Dataset', ...dataset3, }, ], @@ -535,6 +536,7 @@ export const mocks = [ total: 1, entities: [ { + __typename: 'Dataset', ...dataset3, }, ], diff --git a/datahub-web-react/src/app/Routes.tsx b/datahub-web-react/src/app/Routes.tsx index e0b4023554..df1ee2b859 100644 --- a/datahub-web-react/src/app/Routes.tsx +++ b/datahub-web-react/src/app/Routes.tsx @@ -1,15 +1,15 @@ import React from 'react'; import { Switch, Route, RouteProps, Redirect } from 'react-router-dom'; import { useReactiveVar } from '@apollo/client'; -import { BrowseTypesPage } from './browse/BrowseTypesPage'; import { BrowseResultsPage } from './browse/BrowseResultsPage'; -import { SearchPage } from './search/SearchPage'; import { LogIn } from './auth/LogIn'; import { NoPageFound } from './shared/NoPageFound'; import { isLoggedInVar } from './auth/checkAuthStatus'; import { EntityPage } from './entity/EntityPage'; import { PageRoutes } from '../conf/Global'; import { useEntityRegistry } from './useEntityRegistry'; +import { HomePage } from './home/HomePage'; +import { SearchPage } from './search/SearchPage'; const ProtectedRoute = ({ isLoggedIn, @@ -33,7 +33,7 @@ export const Routes = (): JSX.Element => { return (
- } /> + } /> {entityRegistry.getEntities().map((entity) => ( @@ -49,12 +49,6 @@ export const Routes = (): JSX.Element => { path={PageRoutes.SEARCH_RESULTS} render={() => } /> - } - /> = () => { ); if (isLoggedIn) { - // Redirect to search only for Demo Purposes - return ; + return ; } return ( diff --git a/datahub-web-react/src/app/browse/BrowseResultsPage.tsx b/datahub-web-react/src/app/browse/BrowseResultsPage.tsx index e12de081d1..711c5723cd 100644 --- a/datahub-web-react/src/app/browse/BrowseResultsPage.tsx +++ b/datahub-web-react/src/app/browse/BrowseResultsPage.tsx @@ -9,6 +9,7 @@ import { useGetBrowseResultsQuery } from '../../graphql/browse.generated'; import { BrowsePath } from './BrowsePath'; import { PageRoutes } from '../../conf/Global'; import { useEntityRegistry } from '../useEntityRegistry'; +import { Message } from '../shared/Message'; type BrowseResultsPageParams = { type: string; @@ -39,10 +40,6 @@ export const BrowseResultsPage = () => { }, }); - if (loading) { - return ; - } - if (error || (!loading && !error && !data)) { return ; } @@ -63,6 +60,7 @@ export const BrowseResultsPage = () => { + {loading && } {data && data.browse && ( { - const entityRegistry = useEntityRegistry(); - return ( - - -

Browse

- - {entityRegistry.getBrowseEntityTypes().map((entityType) => ( - - - -
- {entityRegistry.getCollectionName(entityType)} -
-
- - - ))} -
-
-
- ); -}; diff --git a/datahub-web-react/src/app/entity/Entity.tsx b/datahub-web-react/src/app/entity/Entity.tsx index 2f7228a670..37a06dea3e 100644 --- a/datahub-web-react/src/app/entity/Entity.tsx +++ b/datahub-web-react/src/app/entity/Entity.tsx @@ -15,6 +15,21 @@ export enum PreviewType { PREVIEW, } +export enum IconStyleType { + /** + * Colored Icon + */ + HIGHLIGHT, + /** + * Grayed out icon + */ + ACCENT, + /** + * Rendered in a Tab pane header + */ + TAB_VIEW, +} + /** * Base interface used for authoring DataHub Entities on the client side. * @@ -26,6 +41,12 @@ export interface Entity { */ type: EntityType; + /** + * Ant-design icon associated with the Entity. For a list of all candidate icons, see + * https://ant.design/components/icon/ + */ + icon: (fontSize: number, styleType: IconStyleType) => JSX.Element; + /** * Returns whether the entity search is enabled */ diff --git a/datahub-web-react/src/app/entity/EntityRegistry.tsx b/datahub-web-react/src/app/entity/EntityRegistry.tsx index 61f9fa523a..60c7b90e20 100644 --- a/datahub-web-react/src/app/entity/EntityRegistry.tsx +++ b/datahub-web-react/src/app/entity/EntityRegistry.tsx @@ -1,5 +1,5 @@ import { EntityType } from '../../types.generated'; -import { Entity, PreviewType } from './Entity'; +import { Entity, IconStyleType, PreviewType } from './Entity'; function validatedGet(key: K, map: Map): V { if (map.has(key)) { @@ -43,6 +43,11 @@ export default class EntityRegistry { return this.entities.filter((entity) => entity.isBrowseEnabled()).map((entity) => entity.type); } + getIcon(type: EntityType, fontSize: number, styleType: IconStyleType): JSX.Element { + const entity = validatedGet(type, this.entityTypeToEntity); + return entity.icon(fontSize, styleType); + } + getCollectionName(type: EntityType): string { const entity = validatedGet(type, this.entityTypeToEntity); return entity.getCollectionName(); diff --git a/datahub-web-react/src/app/entity/chart/ChartEntity.tsx b/datahub-web-react/src/app/entity/chart/ChartEntity.tsx index 85ceb9e901..4602ff90d0 100644 --- a/datahub-web-react/src/app/entity/chart/ChartEntity.tsx +++ b/datahub-web-react/src/app/entity/chart/ChartEntity.tsx @@ -1,6 +1,7 @@ +import { LineChartOutlined } from '@ant-design/icons'; import * as React from 'react'; import { Chart, EntityType } from '../../../types.generated'; -import { Entity, PreviewType } from '../Entity'; +import { Entity, IconStyleType, PreviewType } from '../Entity'; import { ChartPreview } from './preview/ChartPreview'; import ChartProfile from './profile/ChartProfile'; @@ -10,6 +11,25 @@ import ChartProfile from './profile/ChartProfile'; export class ChartEntity implements Entity { type: EntityType = EntityType.Chart; + icon = (fontSize: number, styleType: IconStyleType) => { + if (styleType === IconStyleType.TAB_VIEW) { + return ; + } + + if (styleType === IconStyleType.HIGHLIGHT) { + return ; + } + + return ( + + ); + }; + isSearchEnabled = () => true; isBrowseEnabled = () => false; @@ -29,6 +49,8 @@ export class ChartEntity implements Entity { platform={data.tool} name={data.info?.name} description={data.info?.description} + access={data.info?.access} + owners={data.ownership?.owners} /> ); }; diff --git a/datahub-web-react/src/app/entity/chart/getLogoFromPlatform.tsx b/datahub-web-react/src/app/entity/chart/getLogoFromPlatform.tsx new file mode 100644 index 0000000000..70e4608195 --- /dev/null +++ b/datahub-web-react/src/app/entity/chart/getLogoFromPlatform.tsx @@ -0,0 +1,23 @@ +import lookerLogo from '../../../images/lookerlogo.png'; +import hdfsLogo from '../../../images/hadooplogo.png'; +import kafkaLogo from '../../../images/kafkalogo.png'; +import hiveLogo from '../../../images/hivelogo.png'; + +/** + * TODO: This is a temporary solution, until the backend can push logos for all data platform types. + */ +export function getLogoFromPlatform(platform: string) { + if (platform === 'Looker') { + return lookerLogo; + } + if (platform === 'hdfs') { + return hdfsLogo; + } + if (platform === 'kafka') { + return kafkaLogo; + } + if (platform === 'hive') { + return hiveLogo; + } + return undefined; +} diff --git a/datahub-web-react/src/app/entity/chart/preview/ChartPreview.tsx b/datahub-web-react/src/app/entity/chart/preview/ChartPreview.tsx index 408658d43f..a490e8f600 100644 --- a/datahub-web-react/src/app/entity/chart/preview/ChartPreview.tsx +++ b/datahub-web-react/src/app/entity/chart/preview/ChartPreview.tsx @@ -1,40 +1,45 @@ import React from 'react'; -import { EntityType } from '../../../../types.generated'; +import { AccessLevel, EntityType, Owner } from '../../../../types.generated'; import DefaultPreviewCard from '../../../preview/DefaultPreviewCard'; import { useEntityRegistry } from '../../../useEntityRegistry'; +import { getLogoFromPlatform } from '../getLogoFromPlatform'; export const ChartPreview = ({ urn, name, description, platform, + access, + owners, }: { urn: string; platform: string; name?: string; description?: string | null; + access?: AccessLevel | null; + owners?: Array | null; }): JSX.Element => { const entityRegistry = useEntityRegistry(); return ( {name}
} - > - <> -
{description}
-
- Platform -
{platform}
-
- - + name={name || ''} + description={description || ''} + type="Chart" + logoUrl={getLogoFromPlatform(platform) || ''} + platform={platform} + qualifier={access} + tags={[]} + owners={ + owners?.map((owner) => { + return { + urn: owner.owner.urn, + name: owner.owner.info?.fullName || '', + photoUrl: owner.owner.editableInfo?.pictureLink || '', + }; + }) || [] + } + /> ); }; diff --git a/datahub-web-react/src/app/entity/chart/profile/ChartHeader.tsx b/datahub-web-react/src/app/entity/chart/profile/ChartHeader.tsx index 4e35a68cfd..af95f36c2a 100644 --- a/datahub-web-react/src/app/entity/chart/profile/ChartHeader.tsx +++ b/datahub-web-react/src/app/entity/chart/profile/ChartHeader.tsx @@ -1,10 +1,17 @@ -import { Avatar, Button, Row, Space, Tooltip, Typography } from 'antd'; +import { Avatar, Button, Divider, Row, Space, Tooltip, Typography } from 'antd'; import React from 'react'; import { Link } from 'react-router-dom'; import { AuditStamp, EntityType, Ownership } from '../../../../types.generated'; import { useEntityRegistry } from '../../../useEntityRegistry'; import defaultAvatar from '../../../../images/default_avatar.png'; +const styles = { + content: { width: '100%' }, + typeLabel: { color: 'rgba(0, 0, 0, 0.45)' }, + platformLabel: { color: 'rgba(0, 0, 0, 0.45)' }, + lastUpdatedLabel: { color: 'rgba(0, 0, 0, 0.45)' }, +}; + export type Props = { platform: string; description?: string; @@ -17,37 +24,31 @@ export default function ChartHeader({ platform, description, ownership, url, las const entityRegistry = useEntityRegistry(); return ( - <> + - - {platform} - + }> + Chart + + {platform} + + {url && } {description} - - - {ownership && - ownership.owners && - ownership.owners.map((owner: any) => ( - - - - - - ))} - - {lastModified &&
Last modified at {new Date(lastModified.time).toLocaleDateString('en-US')}
} -
- + + {ownership?.owners?.map((owner: any) => ( + + + + + + ))} + + {lastModified && ( + + Last modified at {new Date(lastModified.time).toLocaleDateString('en-US')} + + )} +
); } diff --git a/datahub-web-react/src/app/entity/chart/profile/ChartProfile.tsx b/datahub-web-react/src/app/entity/chart/profile/ChartProfile.tsx index f5be87fe2a..6caea43330 100644 --- a/datahub-web-react/src/app/entity/chart/profile/ChartProfile.tsx +++ b/datahub-web-react/src/app/entity/chart/profile/ChartProfile.tsx @@ -7,6 +7,7 @@ import { EntityProfile } from '../../../shared/EntityProfile'; import ChartHeader from './ChartHeader'; import { useGetChartQuery } from '../../../../graphql/chart.generated'; import ChartSources from './ChartSources'; +import { Message } from '../../../shared/Message'; const PageContainer = styled.div` background-color: white; @@ -23,10 +24,6 @@ const ENABLED_TAB_TYPES = [TabType.Ownership, TabType.Sources]; export default function ChartProfile({ urn }: { urn: string }) { const { loading, error, data } = useGetChartQuery({ variables: { urn } }); - if (loading) { - return ; - } - if (error || (!loading && !error && !data)) { return ; } @@ -65,6 +62,7 @@ export default function ChartProfile({ urn }: { urn: string }) { return ( <> + {loading && } {data && data.chart && ( ; }; export default function ChartSources({ datasets }: Props) { const entityRegistry = useEntityRegistry(); - return ( - - Source Datasets} - renderItem={(item) => ( - {entityRegistry.renderPreview(EntityType.Dataset, PreviewType.PREVIEW, item)} - )} - /> - + Source Datasets} + renderItem={(item) => ( + + {entityRegistry.renderPreview(EntityType.Dataset, PreviewType.PREVIEW, item)} + + )} + /> ); } diff --git a/datahub-web-react/src/app/entity/dashboard/DashboardEntity.tsx b/datahub-web-react/src/app/entity/dashboard/DashboardEntity.tsx index c98c034c00..f28727c35a 100644 --- a/datahub-web-react/src/app/entity/dashboard/DashboardEntity.tsx +++ b/datahub-web-react/src/app/entity/dashboard/DashboardEntity.tsx @@ -1,6 +1,7 @@ +import { DashboardFilled, DashboardOutlined } from '@ant-design/icons'; import * as React from 'react'; import { Dashboard, EntityType } from '../../../types.generated'; -import { Entity, PreviewType } from '../Entity'; +import { Entity, IconStyleType, PreviewType } from '../Entity'; import { DashboardPreview } from './preview/DashboardPreview'; import DashboardProfile from './profile/DashboardProfile'; @@ -10,6 +11,25 @@ import DashboardProfile from './profile/DashboardProfile'; export class DashboardEntity implements Entity { type: EntityType = EntityType.Dashboard; + icon = (fontSize: number, styleType: IconStyleType) => { + if (styleType === IconStyleType.TAB_VIEW) { + return ; + } + + if (styleType === IconStyleType.HIGHLIGHT) { + return ; + } + + return ( + + ); + }; + isSearchEnabled = () => true; isBrowseEnabled = () => false; @@ -29,6 +49,8 @@ export class DashboardEntity implements Entity { platform={data.tool} name={data.info?.name} description={data.info?.description} + access={data.info?.access} + owners={data.ownership?.owners} /> ); }; diff --git a/datahub-web-react/src/app/entity/dashboard/preview/DashboardPreview.tsx b/datahub-web-react/src/app/entity/dashboard/preview/DashboardPreview.tsx index 94a210cb46..8df60c44a0 100644 --- a/datahub-web-react/src/app/entity/dashboard/preview/DashboardPreview.tsx +++ b/datahub-web-react/src/app/entity/dashboard/preview/DashboardPreview.tsx @@ -1,40 +1,45 @@ import React from 'react'; -import { EntityType } from '../../../../types.generated'; +import { AccessLevel, EntityType, Owner } from '../../../../types.generated'; import DefaultPreviewCard from '../../../preview/DefaultPreviewCard'; import { useEntityRegistry } from '../../../useEntityRegistry'; +import { getLogoFromPlatform } from '../../chart/getLogoFromPlatform'; export const DashboardPreview = ({ urn, name, description, platform, + access, + owners, }: { urn: string; platform: string; name?: string; description?: string | null; + access?: AccessLevel | null; + owners?: Array | null; }): JSX.Element => { const entityRegistry = useEntityRegistry(); return ( {name}} - > - <> -
{description}
-
- Platform -
{platform}
-
- -
+ name={name || ''} + description={description || ''} + type="Dashboard" + logoUrl={getLogoFromPlatform(platform) || ''} + platform={platform} + qualifier={access} + tags={[]} + owners={ + owners?.map((owner) => { + return { + urn: owner.owner.urn, + name: owner.owner.info?.fullName || '', + photoUrl: owner.owner.editableInfo?.pictureLink || '', + }; + }) || [] + } + /> ); }; diff --git a/datahub-web-react/src/app/entity/dashboard/profile/DashboardCharts.tsx b/datahub-web-react/src/app/entity/dashboard/profile/DashboardCharts.tsx index f64866ac03..4826a88fc2 100644 --- a/datahub-web-react/src/app/entity/dashboard/profile/DashboardCharts.tsx +++ b/datahub-web-react/src/app/entity/dashboard/profile/DashboardCharts.tsx @@ -1,9 +1,14 @@ -import { List, Space, Typography } from 'antd'; +import { List, Typography } from 'antd'; import React from 'react'; import { Chart, EntityType } from '../../../../types.generated'; import { useEntityRegistry } from '../../../useEntityRegistry'; import { PreviewType } from '../../Entity'; +const styles = { + list: { marginTop: '12px', padding: '16px 32px' }, + item: { paddingTop: '20px' }, +}; + export type Props = { charts: Array; }; @@ -12,15 +17,16 @@ export default function DashboardCharts({ charts }: Props) { const entityRegistry = useEntityRegistry(); return ( - - Charts} - renderItem={(item) => ( - {entityRegistry.renderPreview(EntityType.Chart, PreviewType.PREVIEW, item)} - )} - /> - + Charts} + renderItem={(item) => ( + + {entityRegistry.renderPreview(EntityType.Chart, PreviewType.PREVIEW, item)} + + )} + /> ); } diff --git a/datahub-web-react/src/app/entity/dashboard/profile/DashboardHeader.tsx b/datahub-web-react/src/app/entity/dashboard/profile/DashboardHeader.tsx index be2f2ab4dd..801a67bd68 100644 --- a/datahub-web-react/src/app/entity/dashboard/profile/DashboardHeader.tsx +++ b/datahub-web-react/src/app/entity/dashboard/profile/DashboardHeader.tsx @@ -1,10 +1,17 @@ -import { Avatar, Button, Row, Space, Tooltip, Typography } from 'antd'; +import { Avatar, Button, Divider, Row, Space, Tooltip, Typography } from 'antd'; import React from 'react'; import { Link } from 'react-router-dom'; import { AuditStamp, EntityType, Ownership } from '../../../../types.generated'; import { useEntityRegistry } from '../../../useEntityRegistry'; import defaultAvatar from '../../../../images/default_avatar.png'; +const styles = { + content: { width: '100%' }, + typeLabel: { color: 'rgba(0, 0, 0, 0.45)' }, + platformLabel: { color: 'rgba(0, 0, 0, 0.45)' }, + lastUpdatedLabel: { color: 'rgba(0, 0, 0, 0.45)' }, +}; + export type Props = { platform: string; description?: string; @@ -16,37 +23,31 @@ export type Props = { export default function DashboardHeader({ platform, description, ownership, url, lastModified }: Props) { const entityRegistry = useEntityRegistry(); return ( - <> + - - {platform} - + }> + Dashboard + + {platform} + + {url && } {description} - - - {ownership && - ownership.owners && - ownership.owners.map((owner: any) => ( - - - - - - ))} - - {lastModified &&
Last modified at {new Date(lastModified.time).toLocaleDateString('en-US')}
} -
- + + {ownership?.owners?.map((owner: any) => ( + + + + + + ))} + + {lastModified && ( + + Last modified at {new Date(lastModified.time).toLocaleDateString('en-US')} + + )} +
); } diff --git a/datahub-web-react/src/app/entity/dashboard/profile/DashboardProfile.tsx b/datahub-web-react/src/app/entity/dashboard/profile/DashboardProfile.tsx index 7397842bb4..02a376a2b1 100644 --- a/datahub-web-react/src/app/entity/dashboard/profile/DashboardProfile.tsx +++ b/datahub-web-react/src/app/entity/dashboard/profile/DashboardProfile.tsx @@ -7,6 +7,7 @@ import { Ownership as OwnershipView } from '../../shared/Ownership'; import { EntityProfile } from '../../../shared/EntityProfile'; import DashboardHeader from './DashboardHeader'; import DashboardCharts from './DashboardCharts'; +import { Message } from '../../../shared/Message'; const PageContainer = styled.div` background-color: white; @@ -26,10 +27,6 @@ const ENABLED_TAB_TYPES = [TabType.Ownership, TabType.Charts]; export default function DashboardProfile({ urn }: { urn: string }) { const { loading, error, data } = useGetDashboardQuery({ variables: { urn } }); - if (loading) { - return ; - } - if (error || (!loading && !error && !data)) { return ; } @@ -68,6 +65,7 @@ export default function DashboardProfile({ urn }: { urn: string }) { return ( <> + {loading && } {data && data.dashboard && ( { type: EntityType = EntityType.Dataset; + icon = (fontSize: number, styleType: IconStyleType) => { + if (styleType === IconStyleType.TAB_VIEW) { + return ; + } + + if (styleType === IconStyleType.HIGHLIGHT) { + return ; + } + + return ( + + ); + }; + isSearchEnabled = () => true; isBrowseEnabled = () => true; @@ -23,13 +43,16 @@ export class DatasetEntity implements Entity { renderProfile = (urn: string) => ; renderPreview = (_: PreviewType, data: Dataset) => { + console.log(data); return ( ); }; diff --git a/datahub-web-react/src/app/entity/dataset/preview/Preview.tsx b/datahub-web-react/src/app/entity/dataset/preview/Preview.tsx index 54202cf2a4..d4509610b0 100644 --- a/datahub-web-react/src/app/entity/dataset/preview/Preview.tsx +++ b/datahub-web-react/src/app/entity/dataset/preview/Preview.tsx @@ -1,54 +1,47 @@ import React from 'react'; -import { EntityType, FabricType, PlatformNativeType } from '../../../../types.generated'; +import { EntityType, FabricType, Owner } from '../../../../types.generated'; import DefaultPreviewCard from '../../../preview/DefaultPreviewCard'; import { useEntityRegistry } from '../../../useEntityRegistry'; +import { getLogoFromPlatform } from '../../chart/getLogoFromPlatform'; export const Preview = ({ urn, name, origin, description, - platformNativeType, + platformName, + tags, + owners, }: { urn: string; name: string; origin: FabricType; description?: string | null; - platformNativeType?: PlatformNativeType | null; + platformName: string; + tags: Array; + owners?: Array | null; }): JSX.Element => { const entityRegistry = useEntityRegistry(); - // TODO: Should we rename the search result card? return ( {name}} - > - <> -
{description}
-
- Data Origin -
{origin}
-
-
- Platform -
{platformNativeType}
-
- -
+ name={name || ''} + description={description || ''} + type="Dataset" + logoUrl={getLogoFromPlatform(platformName) || ''} + platform={platformName} + qualifier={origin} + tags={tags} + owners={ + owners?.map((owner) => { + return { + urn: owner.owner.urn, + name: owner.owner.info?.fullName || '', + photoUrl: owner.owner.editableInfo?.pictureLink || '', + }; + }) || [] + } + /> ); }; diff --git a/datahub-web-react/src/app/entity/dataset/profile/DatasetHeader.tsx b/datahub-web-react/src/app/entity/dataset/profile/DatasetHeader.tsx index 84fab2ecef..0528a64c11 100644 --- a/datahub-web-react/src/app/entity/dataset/profile/DatasetHeader.tsx +++ b/datahub-web-react/src/app/entity/dataset/profile/DatasetHeader.tsx @@ -1,4 +1,4 @@ -import { Avatar, Badge, Popover, Space, Tooltip, Typography } from 'antd'; +import { Avatar, Badge, Divider, Popover, Space, Tooltip, Typography } from 'antd'; import React from 'react'; import { Link } from 'react-router-dom'; import { Dataset, EntityType } from '../../../../types.generated'; @@ -9,32 +9,36 @@ export type Props = { dataset: Dataset; }; -export default function DatasetHeader({ dataset: { description, ownership, deprecation } }: Props) { +export default function DatasetHeader({ dataset: { description, ownership, deprecation, platform } }: Props) { const entityRegistry = useEntityRegistry(); return ( <> - {description} - + + }> + Dataset + + {platform.name} + + + {description} - {ownership && - ownership.owners && - ownership.owners.map((owner: any) => ( - - - - - - ))} + {ownership?.owners?.map((owner: any) => ( + + + + + + ))}
{deprecation?.deprecated && ( diff --git a/datahub-web-react/src/app/entity/dataset/profile/Lineage.tsx b/datahub-web-react/src/app/entity/dataset/profile/Lineage.tsx index fa5d119a8c..e7838676d0 100644 --- a/datahub-web-react/src/app/entity/dataset/profile/Lineage.tsx +++ b/datahub-web-react/src/app/entity/dataset/profile/Lineage.tsx @@ -15,21 +15,27 @@ export default function Lineage({ upstreamLineage, downstreamLineage }: Props) { const downstreamEntities = downstreamLineage?.downstreams.map((downstream) => downstream.dataset); return ( - + Upstream} renderItem={(item) => ( - {entityRegistry.renderPreview(EntityType.Dataset, PreviewType.PREVIEW, item)} + + {entityRegistry.renderPreview(EntityType.Dataset, PreviewType.PREVIEW, item)} + )} /> Downstream} renderItem={(item) => ( - {entityRegistry.renderPreview(EntityType.Dataset, PreviewType.PREVIEW, item)} + + {entityRegistry.renderPreview(EntityType.Dataset, PreviewType.PREVIEW, item)} + )} /> diff --git a/datahub-web-react/src/app/entity/dataset/profile/Profile.tsx b/datahub-web-react/src/app/entity/dataset/profile/Profile.tsx index 7a92e9b653..1e1e288641 100644 --- a/datahub-web-react/src/app/entity/dataset/profile/Profile.tsx +++ b/datahub-web-react/src/app/entity/dataset/profile/Profile.tsx @@ -9,6 +9,7 @@ import LineageView from './Lineage'; import PropertiesView from './Properties'; import DocumentsView from './Documentation'; import DatasetHeader from './DatasetHeader'; +import { Message } from '../../../shared/Message'; export enum TabType { Ownership = 'Ownership', @@ -28,10 +29,6 @@ export const Profile = ({ urn }: { urn: string }): JSX.Element => { const { loading, error, data } = useGetDatasetQuery({ variables: { urn } }); const [updateDataset] = useUpdateDatasetMutation(); - if (loading) { - return ; - } - if (error || (!loading && !error && !data)) { return ; } @@ -92,6 +89,7 @@ export const Profile = ({ urn }: { urn: string }): JSX.Element => { return ( <> + {loading && } {data && data.dataset && ( { type: EntityType = EntityType.CorpUser; + icon = (fontSize: number, styleType: IconStyleType) => { + if (styleType === IconStyleType.TAB_VIEW) { + return ; + } + + if (styleType === IconStyleType.HIGHLIGHT) { + return ; + } + + return ( + + ); + }; + isSearchEnabled = () => true; isBrowseEnabled = () => false; diff --git a/datahub-web-react/src/app/entity/user/__tests__/UserDetails.test.tsx b/datahub-web-react/src/app/entity/user/__tests__/UserDetails.test.tsx index 223dea6b8d..4f41c022e2 100644 --- a/datahub-web-react/src/app/entity/user/__tests__/UserDetails.test.tsx +++ b/datahub-web-react/src/app/entity/user/__tests__/UserDetails.test.tsx @@ -12,12 +12,20 @@ const ownerships = { origin: 'PROD', description: 'this is a dataset', platformNativeType: PlatformNativeType.Table, + platform: { + name: 'hive', + }, + tags: [], }, { name: 'KafkaDataset', origin: 'PROD', description: 'this is also a dataset', platformNativeType: PlatformNativeType.Table, + platform: { + name: 'kafka', + }, + tags: [], }, ], }; diff --git a/datahub-web-react/src/app/entity/user/__tests__/UserOwnership.test.tsx b/datahub-web-react/src/app/entity/user/__tests__/UserOwnership.test.tsx index 4458857a46..1a17cbc272 100644 --- a/datahub-web-react/src/app/entity/user/__tests__/UserOwnership.test.tsx +++ b/datahub-web-react/src/app/entity/user/__tests__/UserOwnership.test.tsx @@ -11,12 +11,20 @@ const ownerships = { origin: 'PROD', description: 'this is a dataset', platformNativeType: PlatformNativeType.Table, + platform: { + name: 'hive', + }, + tags: [], }, { name: 'KafkaDataset', origin: 'PROD', description: 'this is also a dataset', platformNativeType: PlatformNativeType.Table, + platform: { + name: 'kafka', + }, + tags: [], }, ], }; diff --git a/datahub-web-react/src/app/entity/user/preview/Preview.tsx b/datahub-web-react/src/app/entity/user/preview/Preview.tsx index 90e05e4d96..bb4ba9ee8d 100644 --- a/datahub-web-react/src/app/entity/user/preview/Preview.tsx +++ b/datahub-web-react/src/app/entity/user/preview/Preview.tsx @@ -1,10 +1,15 @@ import React from 'react'; import { Avatar, Space, Typography } from 'antd'; +import { Link } from 'react-router-dom'; import { EntityType } from '../../../../types.generated'; -import DefaultPreviewCard from '../../../preview/DefaultPreviewCard'; import { useEntityRegistry } from '../../../useEntityRegistry'; import defaultAvatar from '../../../../images/default_avatar.png'; +const styles = { + name: { margin: 0, color: '#0073b1' }, + title: { color: 'rgba(0, 0, 0, 0.45)' }, +}; + export const Preview = ({ urn, name, @@ -19,19 +24,16 @@ export const Preview = ({ const entityRegistry = useEntityRegistry(); return ( - - - - - {name} - - {title} - + + + + + + {name} + + {title} - } - /> + + ); }; diff --git a/datahub-web-react/src/app/home/HomePage.tsx b/datahub-web-react/src/app/home/HomePage.tsx new file mode 100644 index 0000000000..3ee292b402 --- /dev/null +++ b/datahub-web-react/src/app/home/HomePage.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { HomePageHeader } from './HomePageHeader'; +import { HomePageBody } from './HomePageBody'; + +export const HomePage = () => { + return ( + <> + + + + ); +}; diff --git a/datahub-web-react/src/app/home/HomePageBody.tsx b/datahub-web-react/src/app/home/HomePageBody.tsx new file mode 100644 index 0000000000..57c6f9a146 --- /dev/null +++ b/datahub-web-react/src/app/home/HomePageBody.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { Typography, Row, Col } from 'antd'; +import { useEntityRegistry } from '../useEntityRegistry'; +import { BrowseEntityCard } from '../search/BrowseEntityCard'; + +const styles = { + title: { + margin: '0px 0px 0px 120px', + fontSize: 32, + }, + entityGrid: { + padding: '40px 100px', + }, +}; + +export const HomePageBody = () => { + const entityRegistry = useEntityRegistry(); + return ( + <> + + Explore your data + + + {entityRegistry.getBrowseEntityTypes().map((entityType) => ( + + + + ))} + + + ); +}; diff --git a/datahub-web-react/src/app/home/HomePageHeader.tsx b/datahub-web-react/src/app/home/HomePageHeader.tsx new file mode 100644 index 0000000000..0bfdaedc05 --- /dev/null +++ b/datahub-web-react/src/app/home/HomePageHeader.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { useHistory } from 'react-router'; +import { Typography, Image, Space, AutoComplete, Input, Row } from 'antd'; +import { ManageAccount } from '../shared/ManageAccount'; +import { useGetAuthenticatedUser } from '../useGetAuthenticatedUser'; +import { GlobalCfg, SearchCfg } from '../../conf'; +import { useEntityRegistry } from '../useEntityRegistry'; +import { navigateToSearchUrl } from '../search/utils/navigateToSearchUrl'; +import { useGetAutoCompleteResultsLazyQuery } from '../../graphql/search.generated'; + +const styles = { + background: { + width: '100%', + backgroundImage: 'linear-gradient(#132935, #FFFFFF)', + }, + navBar: { padding: '24px' }, + welcomeText: { color: '#FFFFFF', fontSize: 16 }, + searchContainer: { width: '100%', marginTop: '40px', marginBottom: '160px' }, + logoImage: { width: 140 }, + searchBox: { width: 540, margin: '40px 0px' }, + subHeaderText: { color: '#FFFFFF', fontSize: 20 }, +}; + +export const HomePageHeader = () => { + const history = useHistory(); + const entityRegistry = useEntityRegistry(); + const { data } = useGetAuthenticatedUser(); + const [getAutoCompleteResults, { data: suggestionsData }] = useGetAutoCompleteResultsLazyQuery(); + + const onSearch = (query: string) => { + navigateToSearchUrl({ + query, + history, + entityRegistry, + }); + }; + + const onAutoComplete = (query: string) => { + getAutoCompleteResults({ + variables: { + input: { + type: entityRegistry.getDefaultSearchEntityType(), + query, + }, + }, + }); + }; + + return ( + + + + Welcome back, {data?.corpUser?.info?.firstName || data?.corpUser?.username}. + + + + + + ({ + value: result, + }))} + onSelect={(value: string) => onSearch(value)} + onSearch={(value: string) => onAutoComplete(value)} + > + onSearch(value)} + /> + + + + Find data you can count on. + + + + ); +}; diff --git a/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx b/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx index cfdccfb465..a4fcc15ded 100644 --- a/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx +++ b/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx @@ -1,21 +1,88 @@ -import { Space } from 'antd'; +import { Avatar, Divider, Image, Row, Space, Tag, Tooltip, Typography } from 'antd'; import React from 'react'; import { Link } from 'react-router-dom'; +import { EntityType } from '../../types.generated'; +import defaultAvatar from '../../images/default_avatar.png'; +import { useEntityRegistry } from '../useEntityRegistry'; -interface Props extends React.PropsWithChildren { - title?: React.ReactNode; +interface Props { + name: string; + logoUrl?: string; url: string; + description: string; + type: string; + platform: string; + qualifier?: string | null; + tags: Array; + owners: Array<{ urn: string; name?: string; photoUrl?: string }>; } -export default function DefaultPreviewCard({ title, url, children }: Props) { +const styles = { + row: { width: '100%' }, + leftColumn: { maxWidth: '75%' }, + rightColumn: { maxWidth: '25%' }, + logoImage: { width: '48px' }, + name: { color: '#214F55', fontSize: '18px' }, + typeName: { color: '#585858' }, + platformName: { color: '#585858' }, + ownedBy: { color: '#585858' }, +}; + +export default function DefaultPreviewCard({ + name, + logoUrl, + url, + description, + type, + platform, + qualifier, + tags, + owners, +}: Props) { + const entityRegistry = useEntityRegistry(); return ( - -
- - {title} + + + + + {logoUrl && } + + + {name} + + } size={16}> + {type} + + {platform} + + {qualifier} + + + - {children} -
-
+ {description} +
+ + + {tags.map((tag) => ( + {tag} + ))} + + + + Owned By + + + {owners.map((owner) => ( + + + + + + ))} + + + + ); } diff --git a/datahub-web-react/src/app/search/BrowseEntityCard.tsx b/datahub-web-react/src/app/search/BrowseEntityCard.tsx new file mode 100644 index 0000000000..3162a162bd --- /dev/null +++ b/datahub-web-react/src/app/search/BrowseEntityCard.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import 'antd/dist/antd.css'; +import { Card, Typography, Row } from 'antd'; +import { Link } from 'react-router-dom'; +import '../../App.css'; +import { useEntityRegistry } from '../useEntityRegistry'; +import { PageRoutes } from '../../conf/Global'; +import { IconStyleType } from '../entity/Entity'; +import { EntityType } from '../../types.generated'; + +const styles = { + card: { width: 360 }, + title: { margin: 0, color: '#525252' }, + iconFlag: { right: '32px', top: '-28px' }, + icon: { padding: '16px 24px' }, +}; + +export const BrowseEntityCard = ({ entityType }: { entityType: EntityType }) => { + const entityRegistry = useEntityRegistry(); + return ( + + + + + {entityRegistry.getCollectionName(entityType)} + + + {entityRegistry.getIcon(entityType, 24, IconStyleType.HIGHLIGHT)} + + + + + ); +}; diff --git a/datahub-web-react/src/app/search/SearchPage.tsx b/datahub-web-react/src/app/search/SearchPage.tsx index f033c73d3d..b1d5fb30ee 100644 --- a/datahub-web-react/src/app/search/SearchPage.tsx +++ b/datahub-web-react/src/app/search/SearchPage.tsx @@ -12,15 +12,14 @@ import { useEntityRegistry } from '../useEntityRegistry'; import { FacetFilterInput } from '../../types.generated'; import useFilters from './utils/useFilters'; import { navigateToSearchUrl } from './utils/navigateToSearchUrl'; +import { Message } from '../shared/Message'; type SearchPageParams = { type?: string; }; /** - * A dedicated search page. - * - * TODO: Read / write filter parameters from / to the URL query parameters. + * A search results page. */ export const SearchPage = () => { const history = useHistory(); @@ -51,10 +50,6 @@ export const SearchPage = () => { }, }); - if (loading) { - return ; - } - if (error || (!loading && !error && !data)) { return ; } @@ -79,7 +74,9 @@ export const SearchPage = () => { const toSearchResults = (elements: any) => ( {entityRegistry.renderSearchResult(activeType, item)}} + renderItem={(item) => ( + {entityRegistry.renderSearchResult(activeType, item)} + )} bordered /> ); @@ -104,6 +101,7 @@ export const SearchPage = () => { ))} + {loading && } { , ); - expect(getByText('Loading')).toBeInTheDocument(); + expect(getByText('Loading...')).toBeInTheDocument(); }); it('renders the selected filters as checked', async () => { @@ -87,9 +87,9 @@ describe('SearchPage', () => { const hdfsPlatformBox = getByTestId('facet-platform-hdfs'); expect(hdfsPlatformBox).toHaveProperty('checked', false); - expect(queryByText('Loading')).not.toBeInTheDocument(); + expect(queryByText('Loading...')).not.toBeInTheDocument(); fireEvent.click(hdfsPlatformBox); - expect(queryByText('Loading')).toBeInTheDocument(); + expect(queryByText('Loading...')).toBeInTheDocument(); await waitFor(() => expect(queryByTestId('facet-platform-kafka')).toBeInTheDocument()); diff --git a/datahub-web-react/src/app/shared/Message.tsx b/datahub-web-react/src/app/shared/Message.tsx index 48d515ea08..e07a60a33a 100644 --- a/datahub-web-react/src/app/shared/Message.tsx +++ b/datahub-web-react/src/app/shared/Message.tsx @@ -5,9 +5,10 @@ type MessageType = 'loading' | 'info' | 'error' | 'warning' | 'success'; export type MessageProps = { type: MessageType; content: ReactNode; + style?: React.CSSProperties; }; -export const Message = ({ type, content }: MessageProps): JSX.Element => { +export const Message = ({ type, content, style }: MessageProps): JSX.Element => { const key = useMemo(() => { // We don't actually care about cryptographic security, but instead // just want something unique. That's why it's OK to use Math.random @@ -21,11 +22,12 @@ export const Message = ({ type, content }: MessageProps): JSX.Element => { type, content, duration: 0, + style, }); return () => { hide(); }; - }, [key, type, content]); + }, [key, type, content, style]); return <>; }; diff --git a/datahub-web-react/src/graphql/browse.graphql b/datahub-web-react/src/graphql/browse.graphql index 4b38de29c6..7b37bc5d41 100644 --- a/datahub-web-react/src/graphql/browse.graphql +++ b/datahub-web-react/src/graphql/browse.graphql @@ -13,7 +13,34 @@ query getBrowseResults($input: BrowseInput!) { name origin description - platformNativeType + platform { + name + } + tags + ownership { + owners { + owner { + urn + type + username + info { + active + displayName + title + firstName + lastName + fullName + } + editableInfo { + pictureLink + } + } + type + } + lastModified { + time + } + } } } start diff --git a/datahub-web-react/src/graphql/chart.graphql b/datahub-web-react/src/graphql/chart.graphql index 10dca23d48..a18624a2bc 100644 --- a/datahub-web-react/src/graphql/chart.graphql +++ b/datahub-web-react/src/graphql/chart.graphql @@ -12,6 +12,9 @@ query getChart($urn: String!) { name origin description + platform { + name + } platformNativeType tags lastModified { diff --git a/datahub-web-react/src/graphql/dataset.graphql b/datahub-web-react/src/graphql/dataset.graphql index d8511abc86..5fc0aa96f8 100644 --- a/datahub-web-react/src/graphql/dataset.graphql +++ b/datahub-web-react/src/graphql/dataset.graphql @@ -5,6 +5,9 @@ fragment nonRecursiveDatasetFields on Dataset { origin description uri + platform { + name + } platformNativeType tags properties { @@ -69,13 +72,13 @@ fragment nonRecursiveDatasetFields on Dataset { } } fields { - fieldPath - jsonPath - nullable - description - type - nativeDataType - recursive + fieldPath + jsonPath + nullable + description + type + nativeDataType + recursive } primaryKeys } diff --git a/datahub-web-react/src/graphql/search.graphql b/datahub-web-react/src/graphql/search.graphql index a1738d8875..b7c49691f5 100644 --- a/datahub-web-react/src/graphql/search.graphql +++ b/datahub-web-react/src/graphql/search.graphql @@ -18,6 +18,9 @@ query getSearchResults($input: SearchInput!) { origin description uri + platform { + name + } platformNativeType tags properties { @@ -87,12 +90,24 @@ query getSearchResults($input: SearchInput!) { owners { owner { urn + type + username + info { + active + displayName + title + firstName + lastName + fullName + } + editableInfo { + pictureLink + } } type - source { - type - url - } + } + lastModified { + time } } } @@ -115,12 +130,24 @@ query getSearchResults($input: SearchInput!) { owners { owner { urn + type + username + info { + active + displayName + title + firstName + lastName + fullName + } + editableInfo { + pictureLink + } } type - source { - type - url - } + } + lastModified { + time } } } diff --git a/datahub-web-react/src/images/defaultlogo.png b/datahub-web-react/src/images/defaultlogo.png new file mode 100644 index 0000000000..c5fe459ea6 Binary files /dev/null and b/datahub-web-react/src/images/defaultlogo.png differ diff --git a/datahub-web-react/src/images/hadooplogo.png b/datahub-web-react/src/images/hadooplogo.png new file mode 100644 index 0000000000..049d17111d Binary files /dev/null and b/datahub-web-react/src/images/hadooplogo.png differ diff --git a/datahub-web-react/src/images/hivelogo.png b/datahub-web-react/src/images/hivelogo.png new file mode 100644 index 0000000000..9fe17956ac Binary files /dev/null and b/datahub-web-react/src/images/hivelogo.png differ diff --git a/datahub-web-react/src/images/kafkalogo.png b/datahub-web-react/src/images/kafkalogo.png new file mode 100644 index 0000000000..70a1829ceb Binary files /dev/null and b/datahub-web-react/src/images/kafkalogo.png differ diff --git a/datahub-web-react/src/images/lookerlogo.png b/datahub-web-react/src/images/lookerlogo.png new file mode 100644 index 0000000000..e0f61a1300 Binary files /dev/null and b/datahub-web-react/src/images/lookerlogo.png differ diff --git a/datahub-web-react/src/images/mysqllogo.png b/datahub-web-react/src/images/mysqllogo.png new file mode 100644 index 0000000000..b3e9cebbdc Binary files /dev/null and b/datahub-web-react/src/images/mysqllogo.png differ diff --git a/datahub-web-react/src/images/postgreslogo.png b/datahub-web-react/src/images/postgreslogo.png new file mode 100644 index 0000000000..4fe04107c1 Binary files /dev/null and b/datahub-web-react/src/images/postgreslogo.png differ