Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add loading indicator to Manage Domains sidebar #9142

Merged
merged 4 commits into from Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
84 changes: 50 additions & 34 deletions datahub-web-react/src/app/domain/DomainSearch.tsx
@@ -1,4 +1,5 @@
import React, { CSSProperties, useRef, useState } from 'react';
import { LoadingOutlined } from '@ant-design/icons';
import { Link } from 'react-router-dom';
import styled from 'styled-components/macro';
import Highlight from 'react-highlighter';
Expand Down Expand Up @@ -47,6 +48,13 @@ const SearchResult = styled(Link)`
}
`;

const LoadingWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
height: 350px;
font-size: 30px;
`;
const IconWrapper = styled.span``;

const highlightMatchStyle: CSSProperties = {
Expand All @@ -60,7 +68,7 @@ function DomainSearch() {
const [isSearchBarFocused, setIsSearchBarFocused] = useState(false);
const entityRegistry = useEntityRegistry();

const { data } = useGetSearchResultsForMultipleQuery({
const { data, loading } = useGetSearchResultsForMultipleQuery({
variables: {
input: {
types: [EntityType.Domain],
Expand All @@ -69,7 +77,6 @@ function DomainSearch() {
count: 50,
},
},
skip: !query,
});

const searchResults = data?.searchAcrossEntities?.searchResults;
Expand Down Expand Up @@ -102,38 +109,47 @@ function DomainSearch() {
entityRegistry={entityRegistry}
onFocus={() => setIsSearchBarFocused(true)}
/>
{isSearchBarFocused && searchResults && !!searchResults.length && (
<ResultsWrapper>
{searchResults.map((result) => {
return (
<SearchResult
to={entityRegistry.getEntityUrl(result.entity.type, result.entity.urn)}
onClick={() => setIsSearchBarFocused(false)}
>
<IconWrapper>
{result.entity.type === EntityType.Domain ? (
<DomainIcon
style={{
fontSize: 16,
color: '#BFBFBF',
}}
/>
) : (
entityRegistry.getIcon(result.entity.type, 12, IconStyleType.ACCENT)
)}
</IconWrapper>
<div>
<ParentEntities
parentEntities={getParentDomains(result.entity, entityRegistry)}
/>
<Highlight matchStyle={highlightMatchStyle} search={query}>
{entityRegistry.getDisplayName(result.entity.type, result.entity)}
</Highlight>
</div>
</SearchResult>
);
})}
</ResultsWrapper>
{loading && (
<LoadingWrapper>
<LoadingOutlined />
</LoadingWrapper>
)}
{!loading && (
<>
{isSearchBarFocused && searchResults && !!searchResults.length && (
<ResultsWrapper>
{searchResults.map((result) => {
return (
<SearchResult
sumitappt marked this conversation as resolved.
Show resolved Hide resolved
to={entityRegistry.getEntityUrl(result.entity.type, result.entity.urn)}
onClick={() => setIsSearchBarFocused(false)}
>
<IconWrapper>
{result.entity.type === EntityType.Domain ? (
<DomainIcon
style={{
fontSize: 16,
color: '#BFBFBF',
}}
/>
) : (
entityRegistry.getIcon(result.entity.type, 12, IconStyleType.ACCENT)
)}
</IconWrapper>
<div>
<ParentEntities
parentEntities={getParentDomains(result.entity, entityRegistry)}
/>
<Highlight matchStyle={highlightMatchStyle} search={query}>
{entityRegistry.getDisplayName(result.entity.type, result.entity)}
</Highlight>
</div>
</SearchResult>
);
})}
</ResultsWrapper>
)}
</>
)}
</ClickOutside>
</DomainSearchWrapper>
Expand Down
Expand Up @@ -41,7 +41,7 @@ export const SetDomainModal = ({ urns, onCloseModal, refetch, defaultValue, onOk
}
: undefined,
);
const [domainSearch, { data: domainSearchData }] = useGetSearchResultsLazyQuery();
const [domainSearch, { data: domainSearchData, loading }] = useGetSearchResultsLazyQuery();
const domainSearchResults =
domainSearchData?.search?.searchResults?.map((searchResult) => searchResult.entity) || [];
const [batchSetDomainMutation] = useBatchSetDomainMutation();
Expand Down Expand Up @@ -181,40 +181,42 @@ export const SetDomainModal = ({ urns, onCloseModal, refetch, defaultValue, onOk
</>
}
>
<Form component={false}>
<Form.Item>
<ClickOutside onClickOutside={handleCLickOutside}>
<Select
autoFocus
defaultOpen
filterOption={false}
showSearch
mode="multiple"
defaultActiveFirstOption={false}
placeholder="Search for Domains..."
onSelect={(domainUrn: any) => onSelectDomain(domainUrn)}
onDeselect={onDeselectDomain}
onSearch={(value: string) => {
// eslint-disable-next-line react/prop-types
handleSearch(value.trim());
// eslint-disable-next-line react/prop-types
setInputValue(value.trim());
}}
ref={inputEl}
value={selectValue}
tagRender={tagRender}
onBlur={handleBlur}
onFocus={() => setIsFocusedOnInput(true)}
dropdownStyle={isShowingDomainNavigator ? { display: 'none' } : {}}
>
{domainSearchOptions}
</Select>
<BrowserWrapper isHidden={!isShowingDomainNavigator}>
<DomainNavigator selectDomainOverride={selectDomainFromBrowser} />
</BrowserWrapper>
</ClickOutside>
</Form.Item>
</Form>
<Form component={false}>
<Form.Item>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

<ClickOutside onClickOutside={handleCLickOutside}>
<Select
autoFocus
defaultOpen
filterOption={false}
showSearch
mode="multiple"
defaultActiveFirstOption={false}
placeholder="Search for Domains..."
onSelect={(domainUrn: any) => onSelectDomain(domainUrn)}
onDeselect={onDeselectDomain}
onSearch={(value: string) => {
// eslint-disable-next-line react/prop-types
handleSearch(value.trim());
// eslint-disable-next-line react/prop-types
setInputValue(value.trim());
}}
ref={inputEl}
value={selectValue}
tagRender={tagRender}
onBlur={handleBlur}
onFocus={() => setIsFocusedOnInput(true)}
dropdownStyle={isShowingDomainNavigator ? { display: 'none' } : {}}
loading={loading}
disabled={loading}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we definitely disable this if it's not done loading yet? for example, if they want to search for a specific domain, they should probably be able to do that right away?

not blocking

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As soon as the user opens the modal, we kick off an API call to fetch the Domains. Since the user won't be typing anything initially, it can be a bit confusing to see an empty list when they first focus on the search field.

To avoid this confusion, we disable the search field and show a loader to signal that we're in the process of loading the initial list. Once the domains are ready, we enable the search functionality, allowing the user to start their search.

Even while they're searching, if they momentarily pause typing, we have a timer in place to trigger a search API call. During this time, the search field remains disabled with a loader, indicating that we're actively fetching results that match their input.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be resolved now. No longer any disabled state, as discussed during our standup

>
{domainSearchOptions}
</Select>
<BrowserWrapper isHidden={!isShowingDomainNavigator}>
<DomainNavigator selectDomainOverride={selectDomainFromBrowser} />
</BrowserWrapper>
</ClickOutside>
</Form.Item>
</Form>
</Modal>
);
};