refactor(ui): Adding checkbox option to select multiple results at once. (#5422)

This commit is contained in:
Ankit keshari 2022-07-26 05:04:15 +05:30 committed by GitHub
parent 91ca1e6425
commit f59b21e951
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 273 additions and 52 deletions

View File

@ -4,6 +4,7 @@ import { useHistory, useLocation, useParams } from 'react-router';
import { message } from 'antd'; import { message } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
import { ApolloError } from '@apollo/client'; import { ApolloError } from '@apollo/client';
import type { CheckboxValueType } from 'antd/es/checkbox/Group';
import { useEntityRegistry } from '../../../../../useEntityRegistry'; import { useEntityRegistry } from '../../../../../useEntityRegistry';
import { EntityType, FacetFilterInput } from '../../../../../../types.generated'; import { EntityType, FacetFilterInput } from '../../../../../../types.generated';
@ -97,6 +98,9 @@ export const EmbeddedListSearch = ({
const [showFilters, setShowFilters] = useState(defaultShowFilters || false); const [showFilters, setShowFilters] = useState(defaultShowFilters || false);
const [showSelectMode, setShowSelectMode] = useState(false);
const [checkedSearchResults, setCheckedSearchResults] = useState<CheckboxValueType[]>([]);
const { refetch } = useGetSearchResults({ const { refetch } = useGetSearchResults({
variables: { variables: {
input: { input: {
@ -184,11 +188,13 @@ export const EmbeddedListSearch = ({
onSearch={onSearch} onSearch={onSearch}
placeholderText={placeholderText} placeholderText={placeholderText}
onToggleFilters={toggleFilters} onToggleFilters={toggleFilters}
showDownloadCsvButton
callSearchOnVariables={callSearchOnVariables} callSearchOnVariables={callSearchOnVariables}
entityFilters={entityFilters} entityFilters={entityFilters}
filters={finalFilters} filters={finalFilters}
query={query} query={query}
showSelectMode={showSelectMode}
setShowSelectMode={setShowSelectMode}
checkedSearchResults={checkedSearchResults}
/> />
<EmbeddedListSearchResults <EmbeddedListSearchResults
loading={loading} loading={loading}
@ -199,6 +205,9 @@ export const EmbeddedListSearch = ({
onChangePage={onChangePage} onChangePage={onChangePage}
page={page} page={page}
showFilters={showFilters} showFilters={showFilters}
showSelectMode={showSelectMode}
setCheckedSearchResults={setCheckedSearchResults}
checkedSearchResults={checkedSearchResults}
/> />
</Container> </Container>
); );

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { Button, Typography } from 'antd'; import { Button, Typography } from 'antd';
import { FilterOutlined } from '@ant-design/icons'; import { CloseCircleOutlined, FilterOutlined } from '@ant-design/icons';
import type { CheckboxValueType } from 'antd/es/checkbox/Group';
import styled from 'styled-components'; import styled from 'styled-components';
import TabToolbar from '../TabToolbar'; import TabToolbar from '../TabToolbar';
import { SearchBar } from '../../../../../search/SearchBar'; import { SearchBar } from '../../../../../search/SearchBar';
@ -8,6 +9,7 @@ import { useEntityRegistry } from '../../../../../useEntityRegistry';
import { EntityType, FacetFilterInput, SearchAcrossEntitiesInput } from '../../../../../../types.generated'; import { EntityType, FacetFilterInput, SearchAcrossEntitiesInput } from '../../../../../../types.generated';
import { SearchResultsInterface } from './types'; import { SearchResultsInterface } from './types';
import SearchExtendedMenu from './SearchExtendedMenu'; import SearchExtendedMenu from './SearchExtendedMenu';
// import SearchActionMenu from './SearchActionMenu';
const HeaderContainer = styled.div` const HeaderContainer = styled.div`
display: flex; display: flex;
@ -25,28 +27,38 @@ const SearchMenuContainer = styled.div`
margin-left: 10px; margin-left: 10px;
`; `;
const SelectedText = styled(Typography.Text)`
width: 70px;
top: 5px;
position: relative;
`;
type Props = { type Props = {
onSearch: (q: string) => void; onSearch: (q: string) => void;
onToggleFilters: () => void; onToggleFilters: () => void;
placeholderText?: string | null; placeholderText?: string | null;
showDownloadCsvButton?: boolean;
callSearchOnVariables: (variables: { callSearchOnVariables: (variables: {
input: SearchAcrossEntitiesInput; input: SearchAcrossEntitiesInput;
}) => Promise<SearchResultsInterface | null | undefined>; }) => Promise<SearchResultsInterface | null | undefined>;
entityFilters: EntityType[]; entityFilters: EntityType[];
filters: FacetFilterInput[]; filters: FacetFilterInput[];
query: string; query: string;
setShowSelectMode: (showSelectMode: boolean) => any;
showSelectMode: boolean;
checkedSearchResults: CheckboxValueType[];
}; };
export default function EmbeddedListSearchHeader({ export default function EmbeddedListSearchHeader({
onSearch, onSearch,
onToggleFilters, onToggleFilters,
placeholderText, placeholderText,
showDownloadCsvButton,
callSearchOnVariables, callSearchOnVariables,
entityFilters, entityFilters,
filters, filters,
query, query,
setShowSelectMode,
showSelectMode,
checkedSearchResults,
}: Props) { }: Props) {
const entityRegistry = useEntityRegistry(); const entityRegistry = useEntityRegistry();
@ -62,6 +74,7 @@ export default function EmbeddedListSearchHeader({
<Typography.Text>Filters</Typography.Text> <Typography.Text>Filters</Typography.Text>
</Button> </Button>
<SearchAndDownloadContainer> <SearchAndDownloadContainer>
{showSelectMode && <SelectedText>{`${checkedSearchResults.length} selected`}</SelectedText>}
<SearchBar <SearchBar
initialQuery="" initialQuery=""
placeholderText={placeholderText || 'Search entities...'} placeholderText={placeholderText || 'Search entities...'}
@ -79,13 +92,29 @@ export default function EmbeddedListSearchHeader({
entityRegistry={entityRegistry} entityRegistry={entityRegistry}
/> />
{/* TODO: in the future, when we add more menu items, we'll show this always */} {/* TODO: in the future, when we add more menu items, we'll show this always */}
{showDownloadCsvButton && ( {showSelectMode ? (
<>
<Button
style={{
marginLeft: '5px',
}}
onClick={() => setShowSelectMode(false)}
type="text"
>
<CloseCircleOutlined /> Cancel
</Button>
{/* <SearchMenuContainer>
<SearchActionMenu checkedSearchResults={checkedSearchResults} />
</SearchMenuContainer> */}
</>
) : (
<SearchMenuContainer> <SearchMenuContainer>
<SearchExtendedMenu <SearchExtendedMenu
callSearchOnVariables={callSearchOnVariables} callSearchOnVariables={callSearchOnVariables}
entityFilters={entityFilters} entityFilters={entityFilters}
filters={filters} filters={filters}
query={query} query={query}
setShowSelectMode={setShowSelectMode}
/> />
</SearchMenuContainer> </SearchMenuContainer>
)} )}

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { Pagination, Typography } from 'antd'; import { Pagination, Typography } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
import type { CheckboxValueType } from 'antd/es/checkbox/Group';
import { FacetFilterInput, FacetMetadata, SearchResults as SearchResultType } from '../../../../../../types.generated'; import { FacetFilterInput, FacetMetadata, SearchResults as SearchResultType } from '../../../../../../types.generated';
import { SearchFilters } from '../../../../../search/SearchFilters'; import { SearchFilters } from '../../../../../search/SearchFilters';
import { SearchCfg } from '../../../../../../conf'; import { SearchCfg } from '../../../../../../conf';
@ -94,6 +95,9 @@ interface Props {
showFilters?: boolean; showFilters?: boolean;
onChangeFilters: (filters: Array<FacetFilterInput>) => void; onChangeFilters: (filters: Array<FacetFilterInput>) => void;
onChangePage: (page: number) => void; onChangePage: (page: number) => void;
showSelectMode: boolean;
setCheckedSearchResults: (checkedSearchResults: Array<CheckboxValueType>) => any;
checkedSearchResults: CheckboxValueType[];
} }
export const EmbeddedListSearchResults = ({ export const EmbeddedListSearchResults = ({
@ -105,6 +109,9 @@ export const EmbeddedListSearchResults = ({
showFilters, showFilters,
onChangeFilters, onChangeFilters,
onChangePage, onChangePage,
showSelectMode,
setCheckedSearchResults,
checkedSearchResults,
}: Props) => { }: Props) => {
const pageStart = searchResponse?.start || 0; const pageStart = searchResponse?.start || 0;
const pageSize = searchResponse?.count || 0; const pageSize = searchResponse?.count || 0;
@ -151,6 +158,9 @@ export const EmbeddedListSearchResults = ({
degree: searchResult['degree'], degree: searchResult['degree'],
})) || [] })) || []
} }
showSelectMode={showSelectMode}
setCheckedSearchResults={setCheckedSearchResults}
checkedSearchResults={checkedSearchResults}
/> />
</> </>
)} )}

View File

@ -0,0 +1,55 @@
import React from 'react';
import { Button, Dropdown, Menu } from 'antd';
import { MoreOutlined, PlusOutlined } from '@ant-design/icons';
import styled from 'styled-components';
import type { CheckboxValueType } from 'antd/es/checkbox/Group';
const MenuIcon = styled(MoreOutlined)`
font-size: 15px;
height: 20px;
`;
const SelectButton = styled(Button)`
font-size: 12px;
padding-left: 12px;
padding-right: 12px;
`;
type Props = {
checkedSearchResults: CheckboxValueType[];
};
// currently only contains Download As Csv but will be extended to contain other actions as well
export default function SearchActionMenu({ checkedSearchResults }: Props) {
console.log('checkedSearchResults:: ', checkedSearchResults);
const menu = (
<Menu>
<Menu.Item key="0">
<SelectButton type="text">
<PlusOutlined />
Add Tags
</SelectButton>
</Menu.Item>
<Menu.Item key="1">
<SelectButton type="text">
<PlusOutlined />
Add Terms
</SelectButton>
</Menu.Item>
<Menu.Item key="2">
<SelectButton type="text">
<PlusOutlined />
Add Owners
</SelectButton>
</Menu.Item>
</Menu>
);
return (
<>
<Dropdown overlay={menu} trigger={['click']}>
<MenuIcon />
</Dropdown>
</>
);
}

View File

@ -1,6 +1,8 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Dropdown, Menu } from 'antd'; import { Dropdown, Menu } from 'antd';
import { MoreOutlined } from '@ant-design/icons'; import { MoreOutlined } from '@ant-design/icons';
// import { Button, Dropdown, Menu } from 'antd';
// import { MoreOutlined, SelectOutlined } from '@ant-design/icons';
import styled from 'styled-components'; import styled from 'styled-components';
import { EntityType, FacetFilterInput, SearchAcrossEntitiesInput } from '../../../../../../types.generated'; import { EntityType, FacetFilterInput, SearchAcrossEntitiesInput } from '../../../../../../types.generated';
import { SearchResultsInterface } from './types'; import { SearchResultsInterface } from './types';
@ -12,6 +14,12 @@ const MenuIcon = styled(MoreOutlined)`
height: 20px; height: 20px;
`; `;
// const SelectButton = styled(Button)`
// font-size: 12px;
// padding-left: 12px;
// padding-right: 12px;
// `;
type Props = { type Props = {
callSearchOnVariables: (variables: { callSearchOnVariables: (variables: {
input: SearchAcrossEntitiesInput; input: SearchAcrossEntitiesInput;
@ -19,13 +27,22 @@ type Props = {
entityFilters: EntityType[]; entityFilters: EntityType[];
filters: FacetFilterInput[]; filters: FacetFilterInput[];
query: string; query: string;
setShowSelectMode?: (showSelectMode: boolean) => any;
}; };
// currently only contains Download As Csv but will be extended to contain other actions as well // 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) { export default function SearchExtendedMenu({
callSearchOnVariables,
entityFilters,
filters,
query,
setShowSelectMode,
}: Props) {
const [isDownloadingCsv, setIsDownloadingCsv] = useState(false); const [isDownloadingCsv, setIsDownloadingCsv] = useState(false);
const [showDownloadAsCsvModal, setShowDownloadAsCsvModal] = useState(false); const [showDownloadAsCsvModal, setShowDownloadAsCsvModal] = useState(false);
// TO DO: Need to implement Select Mode
console.log('setShowSelectMode:', setShowSelectMode);
const menu = ( const menu = (
<Menu> <Menu>
<Menu.Item key="0"> <Menu.Item key="0">
@ -34,6 +51,14 @@ export default function SearchExtendedMenu({ callSearchOnVariables, entityFilter
setShowDownloadAsCsvModal={setShowDownloadAsCsvModal} setShowDownloadAsCsvModal={setShowDownloadAsCsvModal}
/> />
</Menu.Item> </Menu.Item>
{/* <Menu.Item key="1">
{setShowSelectMode && (
<SelectButton type="text" onClick={() => setShowSelectMode(true)}>
<SelectOutlined />
Select...
</SelectButton>
)}
</Menu.Item> */}
</Menu> </Menu>
); );

View File

@ -10,11 +10,11 @@ import {
GlossaryTerms, GlossaryTerms,
SearchInsight, SearchInsight,
Container, Container,
Domain,
ParentContainersResult, ParentContainersResult,
Maybe, Maybe,
CorpUser, CorpUser,
Deprecation, Deprecation,
Domain,
} from '../../types.generated'; } from '../../types.generated';
import TagTermGroup from '../shared/tags/TagTermGroup'; import TagTermGroup from '../shared/tags/TagTermGroup';
import { ANTD_GRAY } from '../entity/shared/constants'; import { ANTD_GRAY } from '../entity/shared/constants';
@ -176,7 +176,7 @@ interface Props {
insights?: Array<SearchInsight> | null; insights?: Array<SearchInsight> | null;
glossaryTerms?: GlossaryTerms; glossaryTerms?: GlossaryTerms;
container?: Container; container?: Container;
domain?: Domain | null; domain?: Domain | undefined | null;
entityCount?: number; entityCount?: number;
dataTestID?: string; dataTestID?: string;
titleSizePx?: number; titleSizePx?: number;
@ -239,8 +239,13 @@ export default function DefaultPreviewCard({
const { parentContainersRef, areContainersTruncated } = useParentContainersTruncation(container); const { parentContainersRef, areContainersTruncated } = useParentContainersTruncation(container);
const onPreventMouseDown = (event) => {
event.preventDefault();
event.stopPropagation();
};
return ( return (
<PreviewContainer data-testid={dataTestID}> <PreviewContainer data-testid={dataTestID} onMouseDown={onPreventMouseDown}>
<LeftColumn> <LeftColumn>
<TitleContainer> <TitleContainer>
<PlatformContentView <PlatformContentView

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { Divider, List } from 'antd'; import { Divider, List, Checkbox } from 'antd';
import type { CheckboxValueType } from 'antd/es/checkbox/Group';
import styled from 'styled-components'; import styled from 'styled-components';
import { Entity } from '../../../../types.generated'; import { Entity } from '../../../../types.generated';
import { useEntityRegistry } from '../../../useEntityRegistry'; import { useEntityRegistry } from '../../../useEntityRegistry';
@ -53,6 +54,34 @@ type AdditionalProperties = {
degree?: number; degree?: number;
}; };
const CheckBoxGroup = styled(Checkbox.Group)`
flex: 1;
width: 100%;
background-color: rgb(255, 255, 255);
padding-right: 32px;
padding-left: 32px;
padding-top: 8px;
padding-bottom: 8px;
> .ant-checkbox-group-item {
display: block;
margin-right: 0;
}
&&& .ant-checkbox {
display: inline-block;
position: relative;
top: 48px;
}
`;
const LabelContainer = styled.span`
position: relative;
left: 24px;
bottom: 6px;
* {
pointer-events: none;
}
`;
type Props = { type Props = {
// additional data about the search result that is not part of the entity used to enrich the // additional data about the search result that is not part of the entity used to enrich the
// presentation of the entity. For example, metadata about how the entity is related for the case // presentation of the entity. For example, metadata about how the entity is related for the case
@ -60,9 +89,19 @@ type Props = {
additionalPropertiesList?: Array<AdditionalProperties>; additionalPropertiesList?: Array<AdditionalProperties>;
entities: Array<Entity>; entities: Array<Entity>;
onClick?: (index: number) => void; onClick?: (index: number) => void;
showSelectMode?: boolean;
setCheckedSearchResults?: (checkedSearchResults: Array<CheckboxValueType>) => any;
checkedSearchResults?: CheckboxValueType[];
}; };
export const EntityNameList = ({ additionalPropertiesList, entities, onClick }: Props) => { export const EntityNameList = ({
additionalPropertiesList,
entities,
onClick,
showSelectMode,
setCheckedSearchResults,
checkedSearchResults,
}: Props) => {
const entityRegistry = useEntityRegistry(); const entityRegistry = useEntityRegistry();
if ( if (
additionalPropertiesList?.length !== undefined && additionalPropertiesList?.length !== undefined &&
@ -74,46 +113,95 @@ export const EntityNameList = ({ additionalPropertiesList, entities, onClick }:
{ additionalPropertiesList, entities }, { additionalPropertiesList, entities },
); );
} }
const onChange = (checkedValues: CheckboxValueType[]) => {
setCheckedSearchResults?.(checkedValues);
};
const options = entities.map((entity, index) => {
const additionalProperties = additionalPropertiesList?.[index];
const genericProps = entityRegistry.getGenericEntityProperties(entity.type, entity);
const platformLogoUrl = genericProps?.platform?.properties?.logoUrl;
const platformName =
genericProps?.platform?.properties?.displayName || capitalizeFirstLetter(genericProps?.platform?.name);
const entityTypeName = entityRegistry.getEntityName(entity.type);
const displayName = entityRegistry.getDisplayName(entity.type, entity);
const url = entityRegistry.getEntityUrl(entity.type, entity.urn);
const fallbackIcon = entityRegistry.getIcon(entity.type, 18, IconStyleType.ACCENT);
const subType = genericProps?.subTypes?.typeNames?.length && genericProps?.subTypes?.typeNames[0];
const entityCount = genericProps?.entityCount;
return {
label: (
<LabelContainer>
<DefaultPreviewCard
name={displayName}
logoUrl={platformLogoUrl || undefined}
logoComponent={fallbackIcon}
url={url}
platform={platformName || undefined}
type={subType || entityTypeName}
titleSizePx={14}
tags={genericProps?.globalTags || undefined}
glossaryTerms={genericProps?.glossaryTerms || undefined}
domain={genericProps?.domain?.domain}
onClick={() => onClick?.(index)}
entityCount={entityCount}
degree={additionalProperties?.degree}
/>
<ThinDivider />
</LabelContainer>
),
value: entity.urn,
};
});
return ( return (
<StyledList <>
bordered {showSelectMode ? (
dataSource={entities} <CheckBoxGroup options={options} onChange={onChange} value={checkedSearchResults} />
renderItem={(entity, index) => { ) : (
const additionalProperties = additionalPropertiesList?.[index]; <StyledList
const genericProps = entityRegistry.getGenericEntityProperties(entity.type, entity); bordered
const platformLogoUrl = genericProps?.platform?.properties?.logoUrl; dataSource={entities}
const platformName = renderItem={(entity, index) => {
genericProps?.platform?.properties?.displayName || const additionalProperties = additionalPropertiesList?.[index];
capitalizeFirstLetter(genericProps?.platform?.name); const genericProps = entityRegistry.getGenericEntityProperties(entity.type, entity);
const entityTypeName = entityRegistry.getEntityName(entity.type); const platformLogoUrl = genericProps?.platform?.properties?.logoUrl;
const displayName = entityRegistry.getDisplayName(entity.type, entity); const platformName =
const url = entityRegistry.getEntityUrl(entity.type, entity.urn); genericProps?.platform?.properties?.displayName ||
const fallbackIcon = entityRegistry.getIcon(entity.type, 18, IconStyleType.ACCENT); capitalizeFirstLetter(genericProps?.platform?.name);
const subType = genericProps?.subTypes?.typeNames?.length && genericProps?.subTypes?.typeNames[0]; const entityTypeName = entityRegistry.getEntityName(entity.type);
const entityCount = genericProps?.entityCount; const displayName = entityRegistry.getDisplayName(entity.type, entity);
return ( const url = entityRegistry.getEntityUrl(entity.type, entity.urn);
<> const fallbackIcon = entityRegistry.getIcon(entity.type, 18, IconStyleType.ACCENT);
<ListItem> const subType =
<DefaultPreviewCard genericProps?.subTypes?.typeNames?.length && genericProps?.subTypes?.typeNames[0];
name={displayName} const entityCount = genericProps?.entityCount;
logoUrl={platformLogoUrl || undefined} return (
logoComponent={fallbackIcon} <>
url={url} <ListItem>
platform={platformName || undefined} <DefaultPreviewCard
type={subType || entityTypeName} name={displayName}
titleSizePx={14} logoUrl={platformLogoUrl || undefined}
tags={genericProps?.globalTags || undefined} logoComponent={fallbackIcon}
glossaryTerms={genericProps?.glossaryTerms || undefined} url={url}
domain={genericProps?.domain?.domain} platform={platformName || undefined}
onClick={() => onClick?.(index)} type={subType || entityTypeName}
entityCount={entityCount} titleSizePx={14}
degree={additionalProperties?.degree} tags={genericProps?.globalTags || undefined}
/> glossaryTerms={genericProps?.glossaryTerms || undefined}
</ListItem> domain={genericProps?.domain?.domain}
<ThinDivider /> onClick={() => onClick?.(index)}
</> entityCount={entityCount}
); degree={additionalProperties?.degree}
}} />
/> </ListItem>
<ThinDivider />
</>
);
}}
/>
)}
</>
); );
}; };

View File

@ -26,7 +26,7 @@ type Props = {
editableTags?: GlobalTags | null; editableTags?: GlobalTags | null;
editableGlossaryTerms?: GlossaryTerms | null; editableGlossaryTerms?: GlossaryTerms | null;
uneditableGlossaryTerms?: GlossaryTerms | null; uneditableGlossaryTerms?: GlossaryTerms | null;
domain?: Domain | null; domain?: Domain | undefined | null;
canRemove?: boolean; canRemove?: boolean;
canAddTag?: boolean; canAddTag?: boolean;
canAddTerm?: boolean; canAddTerm?: boolean;