Skip to content

Commit

Permalink
add different styling for filter results
Browse files Browse the repository at this point in the history
  • Loading branch information
clairelin135 committed Mar 9, 2024
1 parent 454c7d9 commit 8755137
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export const AssetsOverview = ({viewerName}: {viewerName?: string}) => {
<AssetGlobalLineageButton />
</Box>
</Box>
<SearchDialog searchPlaceholder="blah" isAssetSearch={true} displayAsOverlay={false} />
<SearchDialog isAssetSearch={true} displayAsOverlay={false} />
</Box>
</Box>
<Box flex={{direction: 'column'}}>
Expand Down
96 changes: 38 additions & 58 deletions js_modules/dagster-ui/packages/ui-core/src/search/SearchDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {useHistory} from 'react-router-dom';
import styled from 'styled-components';

import {SearchResults} from './SearchResults';
import {SearchResult, SearchResultType} from './types';
import {SearchResult, SearchResultType, isAssetFilterSearchResultType} from './types';
import {useGlobalSearch} from './useGlobalSearch';
import {__updateSearchVisibility} from './useSearchVisibility';
import {ShortcutHandler} from '../app/ShortcutHandler';
Expand Down Expand Up @@ -89,11 +89,8 @@ function groupSearchResults(
assetResults: secondaryResults.filter(
(result) => result.item.type === SearchResultType.Asset,
),
assetFilterResults: secondaryResults.filter(
(result) =>
result.item.type === SearchResultType.AssetGroup ||
result.item.type === SearchResultType.ComputeKind ||
result.item.type === SearchResultType.CodeLocation,
assetFilterResults: secondaryResults.filter((result) =>
isAssetFilterSearchResultType(result.item.type),
),
};
} else {
Expand All @@ -112,7 +109,7 @@ export const SearchDialog = ({
isAssetSearch,
displayAsOverlay = true,
}: {
searchPlaceholder: string;
searchPlaceholder?: string;
isAssetSearch: boolean;
displayAsOverlay?: boolean;
}) => {
Expand Down Expand Up @@ -263,6 +260,33 @@ export const SearchDialog = ({
}
};

const searchResults = (
<SearchResults
highlight={highlight}
queryString={queryString}
results={renderedResults}
filterResults={assetFilterResults}
onClickResult={onClickResult}
/>
);

const searchInput = (
<SearchBox hasQueryString={!!queryString.length}>
<Icon name="search" color={Colors.accentGray()} size={20} />
<SearchInput
data-search-input="1"
autoFocus
spellCheck={false}
onChange={onChange}
onKeyDown={onKeyDown}
placeholder={isAssetSearch ? 'Search assets' : 'Search assets, jobs, schedules, sensors…'}
type="text"
value={queryString}
/>
{loading ? <Spinner purpose="body-text" /> : null}
</SearchBox>
);

if (displayAsOverlay) {
return (
<>
Expand Down Expand Up @@ -294,62 +318,18 @@ export const SearchDialog = ({
transitionDuration={100}
>
<Container>
<SearchBox hasQueryString={!!queryString.length}>
<Icon name="search" color={Colors.accentGray()} size={20} />
<SearchInput
data-search-input="1"
autoFocus
spellCheck={false}
onChange={onChange}
onKeyDown={onKeyDown}
placeholder={
isAssetSearch ? 'Search assets' : 'Search assets, jobs, schedules, sensors…'
}
type="text"
value={queryString}
/>
{loading ? <Spinner purpose="body-text" /> : null}
</SearchBox>
<SearchResults
highlight={highlight}
queryString={queryString}
results={renderedResults}
onClickResult={onClickResult}
filterResults={[]}
/>
{searchInput}
{searchResults}
</Container>
</Overlay>
</>
);
} else {
return (
<InPageSearchContainer>
<SearchBox hasQueryString={!!queryString.length}>
<Icon name="search" color={Colors.accentGray()} size={20} />
<SearchInput
data-search-input="1"
autoFocus
spellCheck={false}
onChange={onChange}
onKeyDown={onKeyDown}
placeholder={
isAssetSearch ? 'Search assets' : 'Search assets, jobs, schedules, sensors…'
}
type="text"
value={queryString}
/>
{loading ? <Spinner purpose="body-text" /> : null}
</SearchBox>
<SearchResultsWrapper>
<SearchResults
highlight={highlight}
queryString={queryString}
results={renderedResults}
filterResults={assetFilterResults}
onClickResult={onClickResult}
/>
</SearchResultsWrapper>
</InPageSearchContainer>
<SearchInputWrapper>
{searchInput}
<SearchResultsWrapper>{searchResults}</SearchResultsWrapper>
</SearchInputWrapper>
);
}
};
Expand Down Expand Up @@ -392,7 +372,7 @@ const Container = styled.div`
}
`;

const InPageSearchContainer = styled.div`
const SearchInputWrapper = styled.div`
position: relative;
`;

Expand Down
83 changes: 62 additions & 21 deletions js_modules/dagster-ui/packages/ui-core/src/search/SearchResults.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import {Colors, Icon, IconName, StyledTag} from '@dagster-io/ui-components';
import {Box, Colors, Icon, IconName, StyledTag} from '@dagster-io/ui-components';
import Fuse from 'fuse.js';
import * as React from 'react';
import {Link} from 'react-router-dom';
import styled from 'styled-components';

import {SearchResult, SearchResultType} from './types';
import {
AssetFilterSearchResultType,
SearchResult,
SearchResultType,
isAssetFilterSearchResultType,
} from './types';

const iconForType = (type: SearchResultType): IconName => {
const iconForType = (type: SearchResultType | AssetFilterSearchResultType): IconName => {
switch (type) {
case SearchResultType.Asset:
return 'asset';
Expand All @@ -27,36 +32,56 @@ const iconForType = (type: SearchResultType): IconName => {
return 'op';
case SearchResultType.Resource:
return 'resource';
case AssetFilterSearchResultType.CodeLocation:
return 'folder';
case AssetFilterSearchResultType.Owner:
return 'account_circle';
case AssetFilterSearchResultType.AssetGroup:
return 'asset_group';
default:
return 'source';
}
};

const assetFilterPrefixString = (type: AssetFilterSearchResultType): string => {
switch (type) {
case AssetFilterSearchResultType.CodeLocation:
return 'Code location';
case AssetFilterSearchResultType.ComputeKind:
return 'Compute kind';
case AssetFilterSearchResultType.Owner:
return 'Owner';
case AssetFilterSearchResultType.AssetGroup:
return 'Group';
default:
return '';
}
};

interface ItemProps {
isHighlight: boolean;
onClickResult: (result: Fuse.FuseResult<SearchResult>) => void;
result: Fuse.FuseResult<SearchResult>;
}

function buildSearchLabel(result: Fuse.FuseResult<SearchResult>): JSX.Element[] {
const labelComponents = [];
let parsedString = '';
// Fuse provides indices of the label that match the query string.
// Use these match indices to display the label with the matching parts bolded.

// Match indices can overlap, i.e. [0, 4] and [1, 1] can both be matches
// So we merge them to be non-overlapping
const mergedIndices: Fuse.RangeTuple[] = [];

if (result.matches && result.matches.length > 0) {
const match = result.matches[0]!; // Only one match per row, since we only match by label

// The indices should be returned in sorted order, but we sort just in case
const sortedIndices = Array.from(match.indices).sort((a, b) => (a[0] < b[0] ? -1 : 1));
// Merge overlapping indices
if (sortedIndices.length > 0) {
mergedIndices.push(sortedIndices[0]!);

for (let i = 1; i < sortedIndices.length; i++) {
const last = mergedIndices[mergedIndices.length - 1]!;
const current = sortedIndices[i]!;

if (current[0] <= last[1]) {
last[1] = Math.max(last[1], current[1]);
} else {
Expand All @@ -66,6 +91,8 @@ function buildSearchLabel(result: Fuse.FuseResult<SearchResult>): JSX.Element[]
}
}

const labelComponents = [];
let parsedString = '';
mergedIndices.forEach((indices) => {
const stringBeforeMatch = result.item.label.slice(parsedString.length, indices[0]);
labelComponents.push(<SearchResultLabel>{stringBeforeMatch}</SearchResultLabel>);
Expand Down Expand Up @@ -108,20 +135,34 @@ const SearchResultItem = React.memo(({isHighlight, onClickResult, result}: ItemP
return (
<Item isHighlight={isHighlight} ref={element}>
<ResultLink to={item.href} onMouseDown={onClick}>
<StyledTag
$fillColor={Colors.backgroundGray()}
$interactive={false}
$textColor={Colors.textDefault()}
<Box
flex={{direction: 'row', justifyContent: 'space-between', alignItems: 'center'}}
style={{width: '100%'}}
>
<Icon
name={iconForType(item.type)}
color={isHighlight ? Colors.textDefault() : Colors.textLight()}
/>
{labelComponents.map((component) => component)}
</StyledTag>
<div style={{marginLeft: '12px'}}>
<Description isHighlight={isHighlight}>{item.description}</Description>
</div>
<Box flex={{direction: 'row', alignItems: 'center'}}>
<StyledTag
$fillColor={Colors.backgroundGray()}
$interactive={false}
$textColor={Colors.textDefault()}
>
<Icon
name={iconForType(item.type)}
color={isHighlight ? Colors.textDefault() : Colors.textLight()}
/>
{isAssetFilterSearchResultType(item.type) ? (
<SearchResultLabel>{assetFilterPrefixString(item.type)}:&nbsp;</SearchResultLabel>
) : (
<></>
)}
{labelComponents.map((component) => component)}
</StyledTag>
<div style={{marginLeft: '8px'}}>
<Description isHighlight={isHighlight}>
{item.numResults ? `${item.numResults} assets` : item.description}
</Description>
</div>
</Box>
</Box>
</ResultLink>
</Item>
);
Expand Down

0 comments on commit 8755137

Please sign in to comment.