fix(UI) Fix multiple UI usability issues (#4975)

This commit is contained in:
Chris Collins 2022-05-23 17:21:30 -04:00 committed by GitHub
parent 39fb7e5eb3
commit 3aa841c2e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 351 additions and 160 deletions

View File

@ -1774,6 +1774,14 @@ export const mocks = [
displayName: 'origin',
aggregations: [{ value: 'PROD', count: 3, entity: null }],
},
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
field: 'platform',
displayName: 'platform',
@ -1837,6 +1845,14 @@ export const mocks = [
},
],
},
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
__typename: 'FacetMetadata',
field: 'platform',
@ -1895,6 +1911,14 @@ export const mocks = [
},
],
},
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
field: 'platform',
displayName: 'platform',
@ -1987,6 +2011,14 @@ export const mocks = [
},
],
},
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
field: 'platform',
displayName: 'platform',
@ -2143,6 +2175,14 @@ export const mocks = [
},
],
},
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
field: 'platform',
displayName: 'platform',
@ -2208,6 +2248,14 @@ export const mocks = [
},
],
},
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
field: 'platform',
displayName: 'platform',
@ -2490,6 +2538,26 @@ export const mocks = [
},
],
},
// {
// displayName: 'Domain',
// field: 'domains',
// __typename: 'FacetMetadata',
// aggregations: [
// {
// value: 'urn:li:domain:baedb9f9-98ef-4846-8a0c-2a88680f213e',
// count: 1,
// __typename: 'AggregationMetadata',
// },
// ],
// },
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
__typename: 'FacetMetadata',
field: 'platform',
@ -2665,6 +2733,14 @@ export const mocks = [
},
],
},
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
field: 'platform',
displayName: 'platform',
@ -2731,69 +2807,11 @@ export const mocks = [
],
},
{
field: 'platform',
displayName: 'platform',
field: 'entity',
displayName: 'Type',
aggregations: [
{ value: 'hdfs', count: 1, entity: null },
{ value: 'mysql', count: 1, entity: null },
{ value: 'kafka', count: 1, entity: null },
],
},
],
},
} as GetSearchResultsForMultipleQuery,
},
},
{
request: {
query: GetSearchResultsForMultipleDocument,
variables: {
input: {
types: ['DATASET'],
query: 'test',
start: 0,
count: 10,
filters: [
{
field: 'platform',
value: 'kafka',
},
{
field: 'platform',
value: 'hdfs',
},
],
},
},
},
result: {
data: {
__typename: 'Query',
searchAcrossEntities: {
__typename: 'SearchResults',
start: 0,
count: 1,
total: 1,
searchResults: [
{
entity: {
__typename: 'Dataset',
...dataset3,
},
matchedFields: [],
insights: [],
},
],
facets: [
{
field: 'origin',
displayName: 'origin',
aggregations: [
{
value: 'PROD',
count: 3,
entity: null,
},
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
@ -2862,6 +2880,88 @@ export const mocks = [
},
],
},
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
field: 'platform',
displayName: 'platform',
aggregations: [
{ value: 'hdfs', count: 1, entity: null },
{ value: 'mysql', count: 1, entity: null },
{ value: 'kafka', count: 1, entity: null },
],
},
],
},
} as GetSearchResultsForMultipleQuery,
},
},
{
request: {
query: GetSearchResultsForMultipleDocument,
variables: {
input: {
types: ['DATASET'],
query: 'test',
start: 0,
count: 10,
filters: [
{
field: 'platform',
value: 'kafka',
},
{
field: 'platform',
value: 'hdfs',
},
],
},
},
},
result: {
data: {
__typename: 'Query',
searchAcrossEntities: {
__typename: 'SearchResults',
start: 0,
count: 1,
total: 1,
searchResults: [
{
entity: {
__typename: 'Dataset',
...dataset3,
},
matchedFields: [],
insights: [],
},
],
facets: [
{
field: 'origin',
displayName: 'origin',
aggregations: [
{
value: 'PROD',
count: 3,
entity: null,
},
],
},
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
field: 'platform',
displayName: 'platform',
@ -3096,6 +3196,14 @@ export const mocks = [
},
],
},
{
field: 'entity',
displayName: 'Type',
aggregations: [
{ count: 37, entity: null, value: 'DATASET', __typename: 'AggregationMetadata' },
{ count: 7, entity: null, value: 'CHART', __typename: 'AggregationMetadata' },
],
},
{
field: 'platform',
displayName: 'platform',

View File

@ -8,6 +8,7 @@ import getAvatarColor from '../../../shared/avatar/getAvatarColor';
export type Props = {
users?: (UserUsageCounts | null)[] | null;
maxNumberDisplayed?: number;
};
const AvatarStyled = styled(Avatar)<{ backgroundColor: string }>`
@ -15,12 +16,16 @@ const AvatarStyled = styled(Avatar)<{ backgroundColor: string }>`
background-color: ${(props) => props.backgroundColor};
`;
export default function UsageFacepile({ users }: Props) {
export default function UsageFacepile({ users, maxNumberDisplayed }: Props) {
const sortedUsers = useMemo(() => users?.slice().sort((a, b) => (b?.count || 0) - (a?.count || 0)), [users]);
let displayedUsers = sortedUsers;
if (maxNumberDisplayed) {
displayedUsers = displayedUsers?.slice(0, maxNumberDisplayed);
}
return (
<SpacedAvatarGroup maxCount={2}>
{sortedUsers?.map((user) => (
{displayedUsers?.map((user) => (
<Tooltip title={user?.userEmail}>
<AvatarStyled backgroundColor={getAvatarColor(user?.userEmail || undefined)}>
{user?.userEmail?.charAt(0).toUpperCase()}

View File

@ -100,7 +100,7 @@ export const SidebarStatsSection = () => {
) : null}
{(usageStats?.aggregations?.users?.length || 0) > 0 ? (
<InfoItem title="Top Users" width={INFO_ITEM_WIDTH_PX}>
<UsageFacepile users={usageStats?.aggregations?.users} />
<UsageFacepile users={usageStats?.aggregations?.users} maxNumberDisplayed={10} />
</InfoItem>
) : null}
</StatsRow>

View File

@ -70,7 +70,7 @@ export default function TableStats({
{users && (
<InfoItem title="Top Users">
<div style={{ paddingTop: 8 }}>
<UsageFacepile users={users} />
<UsageFacepile users={users} maxNumberDisplayed={10} />
</div>
</InfoItem>
)}

View File

@ -38,7 +38,7 @@ const styles = {
navBar: { padding: '24px' },
searchContainer: { width: '100%', marginTop: '40px' },
logoImage: { width: 140 },
searchBox: { width: '40vw', minWidth: 400, margin: '40px 0px', marginBottom: '12px' },
searchBox: { width: '47vw', minWidth: 400, margin: '40px 0px', marginBottom: '12px', maxWidth: '650px' },
subtitle: { marginTop: '28px', color: '#FFFFFF', fontSize: 12 },
};
@ -58,8 +58,9 @@ const NavGroup = styled.div`
`;
const SuggestionsContainer = styled.div`
padding: 0px 30px;
max-width: 540px;
margin: 0px 30px;
max-width: 650px;
width: 47vw;
display: flex;
flex-direction: column;
justify-content: left;

View File

@ -1,15 +1,16 @@
import React, { useContext, useMemo, useState } from 'react';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { Group } from '@vx/group';
import { LinkHorizontal } from '@vx/shape';
import styled from 'styled-components';
import { useEntityRegistry } from '../useEntityRegistry';
import { IconStyleType } from '../entity/Entity';
import { NodeData, Direction, VizNode, EntitySelectParams } from './types';
import { NodeData, Direction, VizNode, EntitySelectParams, EntityAndType } from './types';
import { ANTD_GRAY } from '../entity/shared/constants';
import { capitalizeFirstLetter } from '../shared/textUtil';
import { nodeHeightFromTitleLength } from './utils/nodeHeightFromTitleLength';
import { LineageExplorerContext } from './utils/LineageExplorerContext';
import useLazyGetEntityQuery from './utils/useLazyGetEntityQuery';
const CLICK_DELAY_THRESHOLD = 1000;
const DRAG_DISTANCE_THRESHOLD = 20;
@ -81,13 +82,20 @@ export default function LineageEntityNode({
onEntityCenter: (EntitySelectParams) => void;
onHover: (EntitySelectParams) => void;
onDrag: (params: EntitySelectParams, event: React.MouseEvent) => void;
onExpandClick: (LineageExpandParams) => void;
onExpandClick: (data: EntityAndType) => void;
direction: Direction;
nodesToRenderByUrn: Record<string, VizNode>;
}) {
const { expandTitles } = useContext(LineageExplorerContext);
const [isExpanding, setIsExpanding] = useState(false);
const [expandHover, setExpandHover] = useState(false);
const { getAsyncEntity, asyncData } = useLazyGetEntityQuery();
useEffect(() => {
if (asyncData) {
onExpandClick(asyncData);
}
}, [asyncData, onExpandClick]);
const entityRegistry = useEntityRegistry();
const unexploredHiddenChildren =
@ -139,7 +147,9 @@ export default function LineageEntityNode({
<Group
onClick={() => {
setIsExpanding(true);
onExpandClick({ urn: node.data.urn, type: node.data.type, direction });
if (node.data.urn && node.data.type) {
getAsyncEntity(node.data.urn, node.data.type);
}
}}
onMouseOver={() => {
setExpandHover(true);

View File

@ -8,10 +8,9 @@ import styled from 'styled-components';
import { Message } from '../shared/Message';
import { useEntityRegistry } from '../useEntityRegistry';
import CompactContext from '../shared/CompactContext';
import { EntityAndType, EntitySelectParams, FetchedEntities, LineageExpandParams } from './types';
import { EntityAndType, EntitySelectParams, FetchedEntities } from './types';
import LineageViz from './LineageViz';
import extendAsyncEntities from './utils/extendAsyncEntities';
import useLazyGetEntityQuery from './utils/useLazyGetEntityQuery';
import useGetEntityQuery from './utils/useGetEntityQuery';
import { EntityType } from '../../types.generated';
import { capitalizeFirstLetter } from '../shared/textUtil';
@ -58,7 +57,6 @@ export default function LineageExplorer({ urn, type }: Props) {
const entityRegistry = useEntityRegistry();
const { loading, error, data } = useGetEntityQuery(urn, type);
const { getAsyncEntity, asyncData } = useLazyGetEntityQuery();
const [isDrawerVisible, setIsDrawVisible] = useState(false);
const [selectedEntity, setSelectedEntity] = useState<EntitySelectParams | undefined>(undefined);
@ -94,10 +92,7 @@ export default function LineageExplorer({ urn, type }: Props) {
if (type && data) {
maybeAddAsyncLoadedEntity(data);
}
if (asyncData) {
maybeAddAsyncLoadedEntity(asyncData);
}
}, [data, asyncData, asyncEntities, setAsyncEntities, maybeAddAsyncLoadedEntity, urn, previousUrn, type]);
}, [data, asyncEntities, setAsyncEntities, maybeAddAsyncLoadedEntity, urn, previousUrn, type]);
if (error || (!loading && !error && !data)) {
return <Alert type="error" message={error?.message || 'Entity failed to load'} />;
@ -124,8 +119,8 @@ export default function LineageExplorer({ urn, type }: Props) {
`${entityRegistry.getEntityUrl(params.type, params.urn)}/?is_lineage_mode=true`,
);
}}
onLineageExpand={(params: LineageExpandParams) => {
getAsyncEntity(params.urn, params.type);
onLineageExpand={(asyncData: EntityAndType) => {
maybeAddAsyncLoadedEntity(asyncData);
}}
/>
</div>

View File

@ -1,7 +1,7 @@
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { TransformMatrix } from '@vx/zoom/lib/types';
import { NodeData, Direction, EntitySelectParams, TreeProps } from './types';
import { NodeData, Direction, EntitySelectParams, TreeProps, EntityAndType } from './types';
import LineageTreeNodeAndEdgeRenderer from './LineageTreeNodeAndEdgeRenderer';
import layoutTree from './utils/layoutTree';
import { LineageExplorerContext } from './utils/LineageExplorerContext';
@ -13,7 +13,7 @@ type LineageTreeProps = {
};
onEntityClick: (EntitySelectParams) => void;
onEntityCenter: (EntitySelectParams) => void;
onLineageExpand: (LineageExpandParams) => void;
onLineageExpand: (data: EntityAndType) => void;
selectedEntity?: EntitySelectParams;
hoveredEntity?: EntitySelectParams;
setHoveredEntity: (EntitySelectParams) => void;

View File

@ -4,7 +4,7 @@ import { curveBasis } from '@vx/curve';
import { LinePath } from '@vx/shape';
import { TransformMatrix } from '@vx/zoom/lib/types';
import { NodeData, Direction, EntitySelectParams, TreeProps, VizNode, VizEdge } from './types';
import { NodeData, Direction, EntitySelectParams, TreeProps, VizNode, VizEdge, EntityAndType } from './types';
import LineageEntityNode from './LineageEntityNode';
import { ANTD_GRAY } from '../entity/shared/constants';
@ -15,7 +15,7 @@ type Props = {
};
onEntityClick: (EntitySelectParams) => void;
onEntityCenter: (EntitySelectParams) => void;
onLineageExpand: (LineageExpandParams) => void;
onLineageExpand: (data: EntityAndType) => void;
selectedEntity?: EntitySelectParams;
hoveredEntity?: EntitySelectParams;
setHoveredEntity: (EntitySelectParams) => void;

View File

@ -68,7 +68,7 @@ type Props = {
fetchedEntities: { [x: string]: FetchedEntity };
onEntityClick: (EntitySelectParams) => void;
onEntityCenter: (EntitySelectParams) => void;
onLineageExpand: (LineageExpandParams) => void;
onLineageExpand: (data: EntityAndType) => void;
selectedEntity?: EntitySelectParams;
zoom: ProvidedZoom & {
transformMatrix: TransformMatrix;

View File

@ -87,7 +87,7 @@ export type TreeProps = {
fetchedEntities: { [x: string]: FetchedEntity };
onEntityClick: (EntitySelectParams) => void;
onEntityCenter: (EntitySelectParams) => void;
onLineageExpand: (LineageExpandParams) => void;
onLineageExpand: (data: EntityAndType) => void;
selectedEntity?: EntitySelectParams;
hoveredEntity?: EntitySelectParams;
};

View File

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState, useRef } from 'react';
import { Input, AutoComplete, Image, Typography } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import styled from 'styled-components';
@ -37,7 +37,7 @@ const ExploreForEntity = styled.span`
const StyledAutoComplete = styled(AutoComplete)`
width: 100%;
max-width: 475px;
max-width: 650px;
`;
const AutoCompleteContainer = styled.div`
@ -161,6 +161,7 @@ interface Props {
autoCompleteStyle?: React.CSSProperties;
entityRegistry: EntityRegistry;
fixAutoComplete?: boolean;
setIsSearchBarFocused?: (isSearchBarFocused: boolean) => void;
}
const defaultProps = {
@ -181,6 +182,7 @@ export const SearchBar = ({
inputStyle,
autoCompleteStyle,
fixAutoComplete,
setIsSearchBarFocused,
}: Props) => {
const history = useHistory();
const [searchQuery, setSearchQuery] = useState<string>();
@ -249,8 +251,20 @@ export const SearchBar = ({
return emptyQueryOptions;
}, [emptyQueryOptions, autoCompleteEntityOptions, autoCompleteQueryOptions]);
const searchBarWrapperRef = useRef<HTMLDivElement>(null);
function handleSearchBarClick(isSearchBarFocused: boolean) {
if (
setIsSearchBarFocused &&
(!isSearchBarFocused ||
(searchBarWrapperRef && searchBarWrapperRef.current && searchBarWrapperRef.current.clientWidth < 590))
) {
setIsSearchBarFocused(isSearchBarFocused);
}
}
return (
<AutoCompleteContainer style={style}>
<AutoCompleteContainer style={style} ref={searchBarWrapperRef}>
<StyledAutoComplete
defaultActiveFirstOption={false}
style={autoCompleteStyle}
@ -292,6 +306,8 @@ export const SearchBar = ({
onChange={(e) => setSearchQuery(e.target.value)}
data-testid="search-input"
prefix={<SearchOutlined onClick={() => onSearch(filterSearchQuery(searchQuery || ''))} />}
onFocus={() => handleSearchBarClick(true)}
onBlur={() => handleSearchBarClick(false)}
/>
</StyledAutoComplete>
</AutoCompleteContainer>

View File

@ -1,3 +1,4 @@
import { DownOutlined, UpOutlined } from '@ant-design/icons';
import { Button, Checkbox } from 'antd';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
import * as React from 'react';
@ -6,7 +7,7 @@ import styled from 'styled-components';
import { FacetMetadata } from '../../types.generated';
import { SearchFilterLabel } from './SearchFilterLabel';
import { FILTERS_TO_TRUNCATE, TRUNCATED_FILTER_LENGTH } from './utils/constants';
import { TRUNCATED_FILTER_LENGTH } from './utils/constants';
type Props = {
facet: FacetMetadata;
@ -15,6 +16,7 @@ type Props = {
value: string;
}>;
onFilterSelect: (selected: boolean, field: string, value: string) => void;
defaultDisplayFilters: boolean;
};
const SearchFilterWrapper = styled.div`
@ -22,8 +24,11 @@ const SearchFilterWrapper = styled.div`
`;
const Title = styled.div`
align-items: center;
font-weight: bold;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
`;
const CheckBox = styled(Checkbox)`
@ -37,41 +42,63 @@ const ExpandButton = styled(Button)`
}
`;
export const SearchFilter = ({ facet, selectedFilters, onFilterSelect }: Props) => {
const StyledUpOutlined = styled(UpOutlined)`
font-size: 10px;
`;
const StyledDownOutlined = styled(DownOutlined)`
font-size: 10px;
`;
export const SearchFilter = ({ facet, selectedFilters, onFilterSelect, defaultDisplayFilters }: Props) => {
const [areFiltersVisible, setAreFiltersVisible] = useState(defaultDisplayFilters);
const [expanded, setExpanded] = useState(false);
const shouldTruncate =
FILTERS_TO_TRUNCATE.indexOf(facet.field) > -1 && facet.aggregations.length > TRUNCATED_FILTER_LENGTH;
const shouldTruncate = facet.aggregations.length > TRUNCATED_FILTER_LENGTH;
return (
<SearchFilterWrapper key={facet.field}>
<Title>{facet?.displayName}</Title>
{facet.aggregations.map((aggregation, i) => {
if (i >= TRUNCATED_FILTER_LENGTH && !expanded && shouldTruncate) {
return null;
}
return (
<span key={`${facet.field}-${aggregation.value}`}>
<CheckBox
data-testid={`facet-${facet.field}-${aggregation.value}`}
checked={
selectedFilters.find(
(f) => f.field === facet.field && f.value === aggregation.value,
) !== undefined
}
onChange={(e: CheckboxChangeEvent) =>
onFilterSelect(e.target.checked, facet.field, aggregation.value)
}
>
<SearchFilterLabel field={facet.field} aggregation={aggregation} />
</CheckBox>
<br />
</span>
);
})}
{shouldTruncate && (
<ExpandButton type="text" onClick={() => setExpanded(!expanded)}>
{expanded ? '- Less' : '+ More'}
</ExpandButton>
<Title>
{facet?.displayName}
{areFiltersVisible ? (
<StyledUpOutlined onClick={() => setAreFiltersVisible(false)} />
) : (
<StyledDownOutlined
data-testid={`expand-facet-${facet.field}`}
onClick={() => setAreFiltersVisible(true)}
/>
)}
</Title>
{areFiltersVisible && (
<>
{facet.aggregations.map((aggregation, i) => {
if (i >= TRUNCATED_FILTER_LENGTH && !expanded) {
return null;
}
return (
<span key={`${facet.field}-${aggregation.value}`}>
<CheckBox
data-testid={`facet-${facet.field}-${aggregation.value}`}
checked={
selectedFilters.find(
(f) => f.field === facet.field && f.value === aggregation.value,
) !== undefined
}
onChange={(e: CheckboxChangeEvent) =>
onFilterSelect(e.target.checked, facet.field, aggregation.value)
}
>
<SearchFilterLabel field={facet.field} aggregation={aggregation} />
</CheckBox>
<br />
</span>
);
})}
{shouldTruncate && (
<ExpandButton type="text" onClick={() => setExpanded(!expanded)}>
{expanded ? '- Less' : '+ More'}
</ExpandButton>
)}
</>
)}
</SearchFilterWrapper>
);

View File

@ -4,6 +4,8 @@ import { useEffect, useState } from 'react';
import { FacetMetadata } from '../../types.generated';
import { SearchFilter } from './SearchFilter';
const TOP_FILTERS = ['entity', 'tags', 'glossaryTerms', 'domains', 'owners'];
export const SearchFilterWrapper = styled.div`
max-height: 100%;
overflow: auto;
@ -62,14 +64,21 @@ export const SearchFilters = ({ facets, selectedFilters, onFilterSelect, loading
onFilterSelect(newFilters);
};
const sortedFacets = cachedProps.facets.sort((facetA, facetB) => {
if (TOP_FILTERS.indexOf(facetA.field) === -1) return 1;
if (TOP_FILTERS.indexOf(facetB.field) === -1) return -1;
return TOP_FILTERS.indexOf(facetA.field) - TOP_FILTERS.indexOf(facetB.field);
});
return (
<SearchFilterWrapper>
{cachedProps.facets.map((facet) => (
{sortedFacets.map((facet) => (
<SearchFilter
key={`${facet.displayName}-${facet.field}`}
facet={facet}
selectedFilters={cachedProps.selectedFilters}
onFilterSelect={onFilterSelectAndSetCache}
defaultDisplayFilters={TOP_FILTERS.includes(facet.field)}
/>
))}
</SearchFilterWrapper>

View File

@ -1,4 +1,4 @@
import * as React from 'react';
import React, { useState } from 'react';
import { Image, Layout } from 'antd';
import { Link } from 'react-router-dom';
import styled, { useTheme } from 'styled-components';
@ -75,6 +75,7 @@ export const SearchHeader = ({
authenticatedUserPictureLink,
entityRegistry,
}: Props) => {
const [isSearchBarFocused, setIsSearchBarFocused] = useState(false);
const themeConfig = useTheme();
const appConfig = useAppConfig();
@ -98,11 +99,12 @@ export const SearchHeader = ({
onSearch={onSearch}
onQueryChange={onQueryChange}
entityRegistry={entityRegistry}
setIsSearchBarFocused={setIsSearchBarFocused}
fixAutoComplete
/>
</LogoSearchContainer>
<NavGroup>
<AdminHeaderLinks />
<AdminHeaderLinks areLinksHidden={isSearchBarFocused} />
<ManageAccount urn={authenticatedUserUrn} pictureLink={authenticatedUserPictureLink || ''} />
</NavGroup>
</Header>

View File

@ -26,7 +26,6 @@ describe('SearchPage', () => {
});
it('renders the selected filters as checked', async () => {
const promise = Promise.resolve();
const { getByTestId, queryByTestId } = render(
<MockedProvider
mocks={mocks}
@ -44,21 +43,16 @@ describe('SearchPage', () => {
</MockedProvider>,
);
await waitFor(() => expect(queryByTestId('facet-platform-kafka')).toBeInTheDocument());
await waitFor(() => expect(queryByTestId('facet-entity-DATASET')).toBeInTheDocument());
const kafkaPlatformBox = getByTestId('facet-platform-kafka');
expect(kafkaPlatformBox).toHaveProperty('checked', true);
const datasetEntityBox = getByTestId('facet-entity-DATASET');
expect(datasetEntityBox).toHaveProperty('checked', true);
const hdfsPlatformBox = getByTestId('facet-platform-hdfs');
expect(hdfsPlatformBox).toHaveProperty('checked', false);
const prodOriginBox = getByTestId('facet-origin-PROD');
expect(prodOriginBox).toHaveProperty('checked', false);
await act(() => promise);
const chartEntityBox = getByTestId('facet-entity-CHART');
expect(chartEntityBox).toHaveProperty('checked', false);
});
it('renders multiple checked filters at once', async () => {
const promise = Promise.resolve();
const { getByTestId, queryByTestId } = render(
<MockedProvider
mocks={mocks}
@ -76,17 +70,19 @@ describe('SearchPage', () => {
</MockedProvider>,
);
await waitFor(() => expect(queryByTestId('facet-platform-kafka')).toBeInTheDocument());
await waitFor(() => expect(queryByTestId('facet-entity-DATASET')).toBeInTheDocument());
const kafkaPlatformBox = getByTestId('facet-platform-kafka');
expect(kafkaPlatformBox).toHaveProperty('checked', true);
const datasetEntityBox = getByTestId('facet-entity-DATASET');
expect(datasetEntityBox).toHaveProperty('checked', true);
const expandButton = getByTestId('expand-facet-platform');
act(() => {
fireEvent.click(expandButton);
});
await waitFor(() => expect(queryByTestId('facet-platform-hdfs')).toBeInTheDocument());
const hdfsPlatformBox = getByTestId('facet-platform-hdfs');
expect(hdfsPlatformBox).toHaveProperty('checked', true);
const prodOriginBox = getByTestId('facet-origin-PROD');
expect(prodOriginBox).toHaveProperty('checked', false);
await act(() => promise);
});
it('clicking a filter selects a new filter', async () => {
@ -108,24 +104,24 @@ describe('SearchPage', () => {
</MockedProvider>,
);
await waitFor(() => expect(queryByTestId('facet-platform-kafka')).toBeInTheDocument());
await waitFor(() => expect(queryByTestId('facet-entity-DATASET')).toBeInTheDocument());
const kafkaPlatformBox = getByTestId('facet-platform-kafka');
expect(kafkaPlatformBox).toHaveProperty('checked', true);
const datasetEntityBox = getByTestId('facet-entity-DATASET');
expect(datasetEntityBox).toHaveProperty('checked', true);
const hdfsPlatformBox = getByTestId('facet-platform-hdfs');
expect(hdfsPlatformBox).toHaveProperty('checked', false);
const chartEntityBox = getByTestId('facet-entity-CHART');
expect(chartEntityBox).toHaveProperty('checked', false);
act(() => {
fireEvent.click(hdfsPlatformBox);
fireEvent.click(chartEntityBox);
});
await waitFor(() => expect(queryByTestId('facet-platform-kafka')).toBeInTheDocument());
await waitFor(() => expect(queryByTestId('facet-entity-DATASET')).toBeInTheDocument());
const kafkaPlatformBox2 = getByTestId('facet-platform-kafka');
expect(kafkaPlatformBox2).toHaveProperty('checked', true);
const datasetEntityBox2 = getByTestId('facet-entity-DATASET');
expect(datasetEntityBox2).toHaveProperty('checked', true);
const hdfsPlatformBox2 = getByTestId('facet-platform-hdfs');
expect(hdfsPlatformBox2).toHaveProperty('checked', true);
const chartEntityBox2 = getByTestId('facet-entity-CHART');
expect(chartEntityBox2).toHaveProperty('checked', true);
await act(() => promise);
});
});

View File

@ -32,6 +32,10 @@ const DownArrow = styled(CaretDownOutlined)`
color: ${ANTD_GRAY[7]};
`;
const StyledLink = styled(Link)`
white-space: nowrap;
`;
interface Props {
urn: string;
pictureLink?: string;
@ -85,10 +89,10 @@ export const ManageAccount = ({ urn: _urn, pictureLink: _pictureLink, name }: Pr
return (
<Dropdown overlay={menu}>
<Link to={`/${entityRegistry.getPathName(EntityType.CorpUser)}/${_urn}`}>
<StyledLink to={`/${entityRegistry.getPathName(EntityType.CorpUser)}/${_urn}`}>
<CustomAvatar photoUrl={_pictureLink} style={{ marginRight: 5 }} name={name} />
<DownArrow />
</Link>
</StyledLink>
</Dropdown>
);
};

View File

@ -17,7 +17,25 @@ const AdminLink = styled.span`
margin-right: 4px;
`;
export function AdminHeaderLinks() {
const LinksWrapper = styled.div<{ areLinksHidden?: boolean }>`
opacity: 1;
white-space: nowrap;
transition: opacity 0.5s;
${(props) =>
props.areLinksHidden &&
`
opacity: 0;
width: 0;
`}
`;
interface Props {
areLinksHidden?: boolean;
}
export function AdminHeaderLinks(props: Props) {
const { areLinksHidden } = props;
const me = useGetAuthenticatedUser();
const { config } = useAppConfig();
@ -36,7 +54,7 @@ export function AdminHeaderLinks() {
const showDomains = me?.platformPrivileges?.manageDomains || false;
return (
<>
<LinksWrapper areLinksHidden={areLinksHidden}>
{showAnalytics && (
<AdminLink>
<Link to="/analytics">
@ -91,6 +109,6 @@ export function AdminHeaderLinks() {
</Link>
</AdminLink>
)}
</>
</LinksWrapper>
);
}