mirror of
				https://github.com/datahub-project/datahub.git
				synced 2025-11-04 12:51:23 +00:00 
			
		
		
		
	feat(search): embedded view dropdown (#8598)
This commit is contained in:
		
							parent
							
								
									d3fd025b11
								
							
						
					
					
						commit
						0d03f4b7bb
					
				@ -20,6 +20,13 @@ export const ANTD_GRAY = {
 | 
			
		||||
    9: '#434343',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ANTD_GRAY_V2 = {
 | 
			
		||||
    2: '#F3F5F6',
 | 
			
		||||
    5: '#DDE0E4',
 | 
			
		||||
    8: '#5E666E',
 | 
			
		||||
    10: '#1B1E22',
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const EMPTY_MESSAGES = {
 | 
			
		||||
    documentation: {
 | 
			
		||||
        title: 'No documentation yet',
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,17 @@ const MenuButton = styled(MoreOutlined)`
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const MenuStyled = styled(Menu)`
 | 
			
		||||
    &&& {
 | 
			
		||||
        .ant-dropdown-menu-item:not(:hover) {
 | 
			
		||||
            background: none;
 | 
			
		||||
        }
 | 
			
		||||
        .ant-dropdown-menu-item:hover {
 | 
			
		||||
            background: #f5f5f5;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const DEFAULT_VIEW_BUILDER_STATE = {
 | 
			
		||||
    mode: ViewBuilderMode.EDITOR,
 | 
			
		||||
    visible: false,
 | 
			
		||||
@ -233,7 +244,7 @@ export const ViewDropdownMenu = ({
 | 
			
		||||
        <>
 | 
			
		||||
            <Dropdown
 | 
			
		||||
                overlay={
 | 
			
		||||
                    <Menu>
 | 
			
		||||
                    <MenuStyled>
 | 
			
		||||
                        {(canManageView && <EditViewItem key="0" onClick={onEditView} />) || (
 | 
			
		||||
                            <PreviewViewItem key="0" onClick={onPreviewView} />
 | 
			
		||||
                        )}
 | 
			
		||||
@ -247,7 +258,7 @@ export const ViewDropdownMenu = ({
 | 
			
		||||
                            <SetGlobalDefaultItem key="2" onClick={() => setGlobalDefault(view.urn)} />
 | 
			
		||||
                        )}
 | 
			
		||||
                        {canManageView && <DeleteViewItem key="3" onClick={confirmDeleteView} />}
 | 
			
		||||
                    </Menu>
 | 
			
		||||
                    </MenuStyled>
 | 
			
		||||
                }
 | 
			
		||||
                trigger={[trigger]}
 | 
			
		||||
            >
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ type Props = {
 | 
			
		||||
 | 
			
		||||
export const ViewOptionName = ({ name, description }: Props) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <Tooltip placement="left" title={<ViewOptionTooltipTitle name={name} description={description} />}>
 | 
			
		||||
        <Tooltip placement="bottom" showArrow title={<ViewOptionTooltipTitle name={name} description={description} />}>
 | 
			
		||||
            <ViewName>{name}</ViewName>
 | 
			
		||||
        </Tooltip>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -1,24 +1,18 @@
 | 
			
		||||
import React, { useEffect, useRef, useState } from 'react';
 | 
			
		||||
import { useHistory } from 'react-router';
 | 
			
		||||
import { Select } from 'antd';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { VscTriangleDown } from 'react-icons/vsc';
 | 
			
		||||
import { useListMyViewsQuery, useListGlobalViewsQuery } from '../../../../graphql/view.generated';
 | 
			
		||||
import { useUserContext } from '../../../context/useUserContext';
 | 
			
		||||
import { DataHubView, DataHubViewType } from '../../../../types.generated';
 | 
			
		||||
import { ViewBuilder } from '../builder/ViewBuilder';
 | 
			
		||||
import { DEFAULT_LIST_VIEWS_PAGE_SIZE } from '../utils';
 | 
			
		||||
import { PageRoutes } from '../../../../conf/Global';
 | 
			
		||||
import { ViewSelectToolTip } from './ViewSelectToolTip';
 | 
			
		||||
import { ViewBuilderMode } from '../builder/types';
 | 
			
		||||
import { ViewSelectDropdown } from './ViewSelectDropdown';
 | 
			
		||||
import { renderViewOptionGroup } from './renderViewOptionGroup';
 | 
			
		||||
 | 
			
		||||
const selectStyle = {
 | 
			
		||||
    width: 240,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const dropdownStyle = {
 | 
			
		||||
    position: 'fixed',
 | 
			
		||||
} as any;
 | 
			
		||||
import { ANTD_GRAY_V2 } from '../../shared/constants';
 | 
			
		||||
 | 
			
		||||
type ViewBuilderDisplayState = {
 | 
			
		||||
    mode: ViewBuilderMode;
 | 
			
		||||
@ -26,12 +20,46 @@ type ViewBuilderDisplayState = {
 | 
			
		||||
    view?: DataHubView;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const TriangleIcon = styled(VscTriangleDown)<{ isOpen: boolean }>`
 | 
			
		||||
    color: ${(props) => (props.isOpen ? props.theme.styles['primary-color'] : ANTD_GRAY_V2[10])};
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const DEFAULT_VIEW_BUILDER_DISPLAY_STATE = {
 | 
			
		||||
    mode: ViewBuilderMode.EDITOR,
 | 
			
		||||
    visible: false,
 | 
			
		||||
    view: undefined,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ViewSelectContainer = styled.div`
 | 
			
		||||
    &&& {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
 | 
			
		||||
        .ant-select {
 | 
			
		||||
            .ant-select-selection-search {
 | 
			
		||||
                position: absolute;
 | 
			
		||||
            }
 | 
			
		||||
            &.ant-select-open {
 | 
			
		||||
                .ant-select-selection-placeholder,
 | 
			
		||||
                .ant-select-selection-item {
 | 
			
		||||
                    color: ${(props) => props.theme.styles['primary-color']};
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            &:not(.ant-select-open) {
 | 
			
		||||
                .ant-select-selection-placeholder,
 | 
			
		||||
                .ant-select-selection-item {
 | 
			
		||||
                    color: ${ANTD_GRAY_V2[10]};
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .ant-select-selection-placeholder,
 | 
			
		||||
            .ant-select-selection-item {
 | 
			
		||||
                font-weight: 700;
 | 
			
		||||
                font-size: 14px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The View Select component allows you to select a View to apply to query on the current page. For example,
 | 
			
		||||
 * search, recommendations, and browse.
 | 
			
		||||
@ -44,6 +72,7 @@ const DEFAULT_VIEW_BUILDER_DISPLAY_STATE = {
 | 
			
		||||
export const ViewSelect = () => {
 | 
			
		||||
    const history = useHistory();
 | 
			
		||||
    const userContext = useUserContext();
 | 
			
		||||
    const [isOpen, setIsOpen] = useState(false);
 | 
			
		||||
    const [viewBuilderDisplayState, setViewBuilderDisplayState] = useState<ViewBuilderDisplayState>(
 | 
			
		||||
        DEFAULT_VIEW_BUILDER_DISPLAY_STATE,
 | 
			
		||||
    );
 | 
			
		||||
@ -153,54 +182,62 @@ export const ViewSelect = () => {
 | 
			
		||||
        (publicViews.filter((view) => view.urn === selectedUrn)?.length || 0) > 0 ||
 | 
			
		||||
        false;
 | 
			
		||||
 | 
			
		||||
    const handleDropdownVisibleChange = (isNowOpen: boolean) => {
 | 
			
		||||
        setIsOpen(isNowOpen);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <ViewSelectToolTip visible={selectedUrn === undefined}>
 | 
			
		||||
                <Select
 | 
			
		||||
                    data-testid="view-select"
 | 
			
		||||
                    style={selectStyle}
 | 
			
		||||
                    onChange={() => (selectRef?.current as any)?.blur()}
 | 
			
		||||
                    value={(foundSelectedUrn && selectedUrn) || undefined}
 | 
			
		||||
                    placeholder="Select a View"
 | 
			
		||||
                    onSelect={onSelectView}
 | 
			
		||||
                    onClear={onClear}
 | 
			
		||||
                    allowClear
 | 
			
		||||
                    ref={selectRef}
 | 
			
		||||
                    optionLabelProp="label"
 | 
			
		||||
                    dropdownStyle={dropdownStyle}
 | 
			
		||||
                    dropdownRender={(menu) => (
 | 
			
		||||
                        <ViewSelectDropdown
 | 
			
		||||
                            menu={menu}
 | 
			
		||||
                            hasViews={hasViews}
 | 
			
		||||
                            onClickCreateView={onClickCreateView}
 | 
			
		||||
                            onClickClear={onClear}
 | 
			
		||||
                            onClickManageViews={onClickManageViews}
 | 
			
		||||
                        />
 | 
			
		||||
                    )}
 | 
			
		||||
                >
 | 
			
		||||
                    {privateViewCount > 0 &&
 | 
			
		||||
                        renderViewOptionGroup({
 | 
			
		||||
                            views: privateViews,
 | 
			
		||||
                            label: 'Private',
 | 
			
		||||
                            isOwnedByUser: true,
 | 
			
		||||
                            userContext,
 | 
			
		||||
                            hoverViewUrn,
 | 
			
		||||
                            setHoverViewUrn,
 | 
			
		||||
                            onClickEditView,
 | 
			
		||||
                            onClickPreviewView,
 | 
			
		||||
                        })}
 | 
			
		||||
                    {publicViewCount > 0 &&
 | 
			
		||||
                        renderViewOptionGroup({
 | 
			
		||||
                            views: publicViews,
 | 
			
		||||
                            label: 'Public',
 | 
			
		||||
                            userContext,
 | 
			
		||||
                            hoverViewUrn,
 | 
			
		||||
                            setHoverViewUrn,
 | 
			
		||||
                            onClickEditView,
 | 
			
		||||
                            onClickPreviewView,
 | 
			
		||||
                        })}
 | 
			
		||||
                </Select>
 | 
			
		||||
            </ViewSelectToolTip>
 | 
			
		||||
        <ViewSelectContainer>
 | 
			
		||||
            <Select
 | 
			
		||||
                data-testid="view-select"
 | 
			
		||||
                style={{ minWidth: '120px', maxWidth: '200px' }}
 | 
			
		||||
                onChange={() => (selectRef?.current as any)?.blur()}
 | 
			
		||||
                value={(foundSelectedUrn && selectedUrn) || undefined}
 | 
			
		||||
                placeholder="All Entities"
 | 
			
		||||
                onSelect={onSelectView}
 | 
			
		||||
                onClear={onClear}
 | 
			
		||||
                ref={selectRef}
 | 
			
		||||
                optionLabelProp="label"
 | 
			
		||||
                bordered={false}
 | 
			
		||||
                dropdownMatchSelectWidth={false}
 | 
			
		||||
                suffixIcon={<TriangleIcon isOpen={isOpen} />}
 | 
			
		||||
                dropdownStyle={{
 | 
			
		||||
                    position: 'fixed',
 | 
			
		||||
                    paddingBottom: 0,
 | 
			
		||||
                }}
 | 
			
		||||
                onDropdownVisibleChange={handleDropdownVisibleChange}
 | 
			
		||||
                dropdownRender={(menu) => (
 | 
			
		||||
                    <ViewSelectDropdown
 | 
			
		||||
                        menu={menu}
 | 
			
		||||
                        hasViews={hasViews}
 | 
			
		||||
                        onClickCreateView={onClickCreateView}
 | 
			
		||||
                        onClickClear={onClear}
 | 
			
		||||
                        onClickManageViews={onClickManageViews}
 | 
			
		||||
                    />
 | 
			
		||||
                )}
 | 
			
		||||
            >
 | 
			
		||||
                {privateViewCount > 0 &&
 | 
			
		||||
                    renderViewOptionGroup({
 | 
			
		||||
                        views: privateViews,
 | 
			
		||||
                        label: 'Private',
 | 
			
		||||
                        isOwnedByUser: true,
 | 
			
		||||
                        userContext,
 | 
			
		||||
                        hoverViewUrn,
 | 
			
		||||
                        setHoverViewUrn,
 | 
			
		||||
                        onClickEditView,
 | 
			
		||||
                        onClickPreviewView,
 | 
			
		||||
                    })}
 | 
			
		||||
                {publicViewCount > 0 &&
 | 
			
		||||
                    renderViewOptionGroup({
 | 
			
		||||
                        views: publicViews,
 | 
			
		||||
                        label: 'Public',
 | 
			
		||||
                        userContext,
 | 
			
		||||
                        hoverViewUrn,
 | 
			
		||||
                        setHoverViewUrn,
 | 
			
		||||
                        onClickEditView,
 | 
			
		||||
                        onClickPreviewView,
 | 
			
		||||
                    })}
 | 
			
		||||
            </Select>
 | 
			
		||||
            {viewBuilderDisplayState.visible && (
 | 
			
		||||
                <ViewBuilder
 | 
			
		||||
                    urn={viewBuilderDisplayState.view?.urn || undefined}
 | 
			
		||||
@ -210,6 +247,6 @@ export const ViewSelect = () => {
 | 
			
		||||
                    onCancel={onCloseViewBuilder}
 | 
			
		||||
                />
 | 
			
		||||
            )}
 | 
			
		||||
        </>
 | 
			
		||||
        </ViewSelectContainer>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -13,9 +13,13 @@ type Props = {
 | 
			
		||||
export const ViewSelectDropdown = ({ menu, hasViews, onClickCreateView, onClickManageViews, onClickClear }: Props) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <ViewSelectHeader onClickCreateView={onClickCreateView} onClickClear={onClickClear} />
 | 
			
		||||
            <ViewSelectHeader onClickClear={onClickClear} />
 | 
			
		||||
            {hasViews && menu}
 | 
			
		||||
            <ViewSelectFooter onClickManageViews={onClickManageViews} />
 | 
			
		||||
            <ViewSelectFooter
 | 
			
		||||
                hasViews={hasViews}
 | 
			
		||||
                onClickCreateView={onClickCreateView}
 | 
			
		||||
                onClickManageViews={onClickManageViews}
 | 
			
		||||
            />
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,33 +1,47 @@
 | 
			
		||||
import React, { useRef } from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { Button } from 'antd';
 | 
			
		||||
import { RightOutlined } from '@ant-design/icons';
 | 
			
		||||
import { ANTD_GRAY } from '../../shared/constants';
 | 
			
		||||
import { Button, Typography } from 'antd';
 | 
			
		||||
import { PlusOutlined } from '@ant-design/icons';
 | 
			
		||||
import { ANTD_GRAY, ANTD_GRAY_V2 } from '../../shared/constants';
 | 
			
		||||
import { NoMarginButton } from './styledComponents';
 | 
			
		||||
 | 
			
		||||
const ButtonContainer = styled.div`
 | 
			
		||||
    display: flex;
 | 
			
		||||
    color: ${ANTD_GRAY[6]};
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    justify-content: left;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const StyledRightOutlined = styled(RightOutlined)`
 | 
			
		||||
    && {
 | 
			
		||||
        font-size: 8px;
 | 
			
		||||
        color: ${ANTD_GRAY[7]};
 | 
			
		||||
const CreateViewButton = styled(NoMarginButton)<{ bordered: boolean }>`
 | 
			
		||||
    &&& {
 | 
			
		||||
        text-align: left;
 | 
			
		||||
        font-weight: normal;
 | 
			
		||||
        border-top-left-radius: 0;
 | 
			
		||||
        border-top-right-radius: 0;
 | 
			
		||||
        margin-left: 8px;
 | 
			
		||||
        margin-right: 8px;
 | 
			
		||||
        padding-left: 0px;
 | 
			
		||||
 | 
			
		||||
        ${(props) => props.bordered && `border-top: 1px solid ${ANTD_GRAY_V2[5]};`}
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ManageViewsButton = styled(Button)`
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    color: ${ANTD_GRAY[7]};
 | 
			
		||||
    &&& {
 | 
			
		||||
        color: ${(props) => props.theme.styles['primary-color']};
 | 
			
		||||
        border-top: 1px solid ${ANTD_GRAY_V2[5]};
 | 
			
		||||
        background-color: ${ANTD_GRAY_V2[2]};
 | 
			
		||||
        border-top-left-radius: 0;
 | 
			
		||||
        border-top-right-radius: 0;
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    hasViews: boolean;
 | 
			
		||||
    onClickCreateView: () => void;
 | 
			
		||||
    onClickManageViews: () => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ViewSelectFooter = ({ onClickManageViews }: Props) => {
 | 
			
		||||
export const ViewSelectFooter = ({ hasViews, onClickCreateView, onClickManageViews }: Props) => {
 | 
			
		||||
    const manageViewsButtonRef = useRef(null);
 | 
			
		||||
 | 
			
		||||
    const onHandleClickManageViews = () => {
 | 
			
		||||
@ -37,9 +51,17 @@ export const ViewSelectFooter = ({ onClickManageViews }: Props) => {
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <ButtonContainer>
 | 
			
		||||
            <CreateViewButton
 | 
			
		||||
                data-testid="view-select-create"
 | 
			
		||||
                type="text"
 | 
			
		||||
                bordered={hasViews}
 | 
			
		||||
                onClick={onClickCreateView}
 | 
			
		||||
            >
 | 
			
		||||
                <PlusOutlined />
 | 
			
		||||
                <Typography.Text>Create new view</Typography.Text>
 | 
			
		||||
            </CreateViewButton>
 | 
			
		||||
            <ManageViewsButton type="text" ref={manageViewsButtonRef} onClick={onHandleClickManageViews}>
 | 
			
		||||
                Manage Views
 | 
			
		||||
                <StyledRightOutlined />
 | 
			
		||||
            </ManageViewsButton>
 | 
			
		||||
        </ButtonContainer>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -1,25 +1,32 @@
 | 
			
		||||
import React, { useRef } from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { Button, Typography } from 'antd';
 | 
			
		||||
import { PlusOutlined } from '@ant-design/icons';
 | 
			
		||||
import { NoMarginButton } from './styledComponents';
 | 
			
		||||
import { ANTD_GRAY_V2 } from '../../shared/constants';
 | 
			
		||||
 | 
			
		||||
const ButtonContainer = styled.div`
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const NoMarginButton = styled(Button)`
 | 
			
		||||
    && {
 | 
			
		||||
        margin: 0px;
 | 
			
		||||
const AllEntitiesButton = styled(NoMarginButton)`
 | 
			
		||||
    &&& {
 | 
			
		||||
        font-weight: normal;
 | 
			
		||||
        border-bottom: 1px solid ${ANTD_GRAY_V2[5]};
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        text-align: left;
 | 
			
		||||
        border-bottom-left-radius: 0;
 | 
			
		||||
        border-bottom-right-radius: 0;
 | 
			
		||||
        margin-left: 8px;
 | 
			
		||||
        margin-right: 8px;
 | 
			
		||||
        padding-left: 0px;
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    onClickCreateView: () => void;
 | 
			
		||||
    onClickClear: () => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ViewSelectHeader = ({ onClickCreateView, onClickClear }: Props) => {
 | 
			
		||||
export const ViewSelectHeader = ({ onClickClear }: Props) => {
 | 
			
		||||
    const clearButtonRef = useRef(null);
 | 
			
		||||
 | 
			
		||||
    const onHandleClickClear = () => {
 | 
			
		||||
@ -29,18 +36,14 @@ export const ViewSelectHeader = ({ onClickCreateView, onClickClear }: Props) =>
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <ButtonContainer>
 | 
			
		||||
            <NoMarginButton data-testid="view-select-create" type="text" onClick={onClickCreateView}>
 | 
			
		||||
                <PlusOutlined />
 | 
			
		||||
                <Typography.Text strong> Create View</Typography.Text>
 | 
			
		||||
            </NoMarginButton>
 | 
			
		||||
            <NoMarginButton
 | 
			
		||||
            <AllEntitiesButton
 | 
			
		||||
                data-testid="view-select-clear"
 | 
			
		||||
                type="link"
 | 
			
		||||
                type="text"
 | 
			
		||||
                ref={clearButtonRef}
 | 
			
		||||
                onClick={onHandleClickClear}
 | 
			
		||||
            >
 | 
			
		||||
                Clear
 | 
			
		||||
            </NoMarginButton>
 | 
			
		||||
                All Entities
 | 
			
		||||
            </AllEntitiesButton>
 | 
			
		||||
        </ButtonContainer>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,29 +0,0 @@
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { Tooltip } from 'antd';
 | 
			
		||||
 | 
			
		||||
const HeaderText = styled.div`
 | 
			
		||||
    margin-bottom: 8px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    children: React.ReactNode;
 | 
			
		||||
    visible?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ViewSelectToolTip = ({ children, visible = true }: Props) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <Tooltip
 | 
			
		||||
            overlayStyle={{ display: !visible ? 'none' : undefined }}
 | 
			
		||||
            placement="right"
 | 
			
		||||
            title={
 | 
			
		||||
                <>
 | 
			
		||||
                    <HeaderText>Select a View to apply to search results.</HeaderText>
 | 
			
		||||
                    <div>Views help narrow down search results to those that matter most to you.</div>
 | 
			
		||||
                </>
 | 
			
		||||
            }
 | 
			
		||||
        >
 | 
			
		||||
            {children}
 | 
			
		||||
        </Tooltip>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
@ -0,0 +1,17 @@
 | 
			
		||||
import { RightOutlined } from '@ant-design/icons';
 | 
			
		||||
import { Button } from 'antd';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { ANTD_GRAY } from '../../shared/constants';
 | 
			
		||||
 | 
			
		||||
export const NoMarginButton = styled(Button)`
 | 
			
		||||
    && {
 | 
			
		||||
        margin: 0px;
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export const StyledRightOutlined = styled(RightOutlined)`
 | 
			
		||||
    && {
 | 
			
		||||
        font-size: 8px;
 | 
			
		||||
        color: ${ANTD_GRAY[7]};
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
@ -152,6 +152,7 @@ export const HomePageHeader = () => {
 | 
			
		||||
    const showAcrylInfo = useIsShowAcrylInfoEnabled();
 | 
			
		||||
    const { user } = userContext;
 | 
			
		||||
    const viewUrn = userContext.localState?.selectedViewUrn;
 | 
			
		||||
    const viewsEnabled = appConfig.config?.viewsConfig?.enabled || false;
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (suggestionsData !== undefined) {
 | 
			
		||||
@ -271,6 +272,7 @@ export const HomePageHeader = () => {
 | 
			
		||||
                        onQueryChange={onAutoComplete}
 | 
			
		||||
                        autoCompleteStyle={styles.searchBox}
 | 
			
		||||
                        entityRegistry={entityRegistry}
 | 
			
		||||
                        viewsEnabled={viewsEnabled}
 | 
			
		||||
                        showQuickFilters
 | 
			
		||||
                    />
 | 
			
		||||
                    {searchResultsToShow && searchResultsToShow.length > 0 && (
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import React, { useEffect, useMemo, useState, useRef, useCallback } from 'react';
 | 
			
		||||
import React, { useEffect, useMemo, useState, useRef, useCallback, EventHandler, SyntheticEvent } from 'react';
 | 
			
		||||
import { Input, AutoComplete, Button } from 'antd';
 | 
			
		||||
import { CloseCircleFilled, SearchOutlined } from '@ant-design/icons';
 | 
			
		||||
import styled from 'styled-components/macro';
 | 
			
		||||
@ -6,7 +6,7 @@ import { useHistory } from 'react-router';
 | 
			
		||||
import { AutoCompleteResultForEntity, Entity, EntityType, FacetFilterInput, ScenarioType } from '../../types.generated';
 | 
			
		||||
import EntityRegistry from '../entity/EntityRegistry';
 | 
			
		||||
import filterSearchQuery from './utils/filterSearchQuery';
 | 
			
		||||
import { ANTD_GRAY } from '../entity/shared/constants';
 | 
			
		||||
import { ANTD_GRAY, ANTD_GRAY_V2 } from '../entity/shared/constants';
 | 
			
		||||
import { getEntityPath } from '../entity/shared/containers/profile/utils';
 | 
			
		||||
import { EXACT_SEARCH_PREFIX } from './utils/constants';
 | 
			
		||||
import { useListRecommendationsQuery } from '../../graphql/recommendations.generated';
 | 
			
		||||
@ -22,6 +22,7 @@ import { useUserContext } from '../context/useUserContext';
 | 
			
		||||
import { navigateToSearchUrl } from './utils/navigateToSearchUrl';
 | 
			
		||||
import { getQuickFilterDetails } from './autoComplete/quickFilters/utils';
 | 
			
		||||
import ViewAllSearchItem from './ViewAllSearchItem';
 | 
			
		||||
import { ViewSelect } from '../entity/view/select/ViewSelect';
 | 
			
		||||
 | 
			
		||||
const StyledAutoComplete = styled(AutoComplete)`
 | 
			
		||||
    width: 100%;
 | 
			
		||||
@ -39,9 +40,14 @@ const StyledSearchBar = styled(Input)`
 | 
			
		||||
        height: 40px;
 | 
			
		||||
        font-size: 20px;
 | 
			
		||||
        color: ${ANTD_GRAY[7]};
 | 
			
		||||
        background-color: ${ANTD_GRAY_V2[2]};
 | 
			
		||||
    }
 | 
			
		||||
    > .ant-input {
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
        background-color: ${ANTD_GRAY_V2[2]};
 | 
			
		||||
    }
 | 
			
		||||
    > .ant-input::placeholder {
 | 
			
		||||
        color: ${ANTD_GRAY_V2[10]};
 | 
			
		||||
    }
 | 
			
		||||
    .ant-input-clear-icon {
 | 
			
		||||
        height: 15px;
 | 
			
		||||
@ -56,6 +62,16 @@ const ClearIcon = styled(CloseCircleFilled)`
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ViewSelectContainer = styled.div`
 | 
			
		||||
    &&& {
 | 
			
		||||
        border-right: 1px solid ${ANTD_GRAY_V2[5]};
 | 
			
		||||
    }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const SearchIcon = styled(SearchOutlined)`
 | 
			
		||||
    color: ${ANTD_GRAY_V2[8]};
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const EXACT_AUTOCOMPLETE_OPTION_TYPE = 'exact_query';
 | 
			
		||||
const RECOMMENDED_QUERY_OPTION_TYPE = 'recommendation';
 | 
			
		||||
 | 
			
		||||
@ -89,6 +105,10 @@ const renderRecommendedQuery = (query: string) => {
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleStopPropagation: EventHandler<SyntheticEvent> = (e) => {
 | 
			
		||||
    e.stopPropagation();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
    initialQuery?: string;
 | 
			
		||||
    placeholderText: string;
 | 
			
		||||
@ -102,6 +122,7 @@ interface Props {
 | 
			
		||||
    fixAutoComplete?: boolean;
 | 
			
		||||
    hideRecommendations?: boolean;
 | 
			
		||||
    showQuickFilters?: boolean;
 | 
			
		||||
    viewsEnabled?: boolean;
 | 
			
		||||
    setIsSearchBarFocused?: (isSearchBarFocused: boolean) => void;
 | 
			
		||||
    onFocus?: () => void;
 | 
			
		||||
    onBlur?: () => void;
 | 
			
		||||
@ -127,6 +148,7 @@ export const SearchBar = ({
 | 
			
		||||
    fixAutoComplete,
 | 
			
		||||
    hideRecommendations,
 | 
			
		||||
    showQuickFilters,
 | 
			
		||||
    viewsEnabled = false,
 | 
			
		||||
    setIsSearchBarFocused,
 | 
			
		||||
    onFocus,
 | 
			
		||||
    onBlur,
 | 
			
		||||
@ -326,6 +348,7 @@ export const SearchBar = ({
 | 
			
		||||
                listHeight={480}
 | 
			
		||||
            >
 | 
			
		||||
                <StyledSearchBar
 | 
			
		||||
                    bordered={false}
 | 
			
		||||
                    placeholder={placeholderText}
 | 
			
		||||
                    onPressEnter={() => {
 | 
			
		||||
                        handleSearch(
 | 
			
		||||
@ -334,7 +357,7 @@ export const SearchBar = ({
 | 
			
		||||
                            getFiltersWithQuickFilter(selectedQuickFilter),
 | 
			
		||||
                        );
 | 
			
		||||
                    }}
 | 
			
		||||
                    style={inputStyle}
 | 
			
		||||
                    style={{ ...inputStyle, color: 'red' }}
 | 
			
		||||
                    value={searchQuery}
 | 
			
		||||
                    onChange={(e) => setSearchQuery(e.target.value)}
 | 
			
		||||
                    data-testid="search-input"
 | 
			
		||||
@ -342,15 +365,28 @@ export const SearchBar = ({
 | 
			
		||||
                    onBlur={handleBlur}
 | 
			
		||||
                    allowClear={{ clearIcon: <ClearIcon /> }}
 | 
			
		||||
                    prefix={
 | 
			
		||||
                        <SearchOutlined
 | 
			
		||||
                            onClick={() => {
 | 
			
		||||
                                handleSearch(
 | 
			
		||||
                                    filterSearchQuery(searchQuery || ''),
 | 
			
		||||
                                    undefined,
 | 
			
		||||
                                    getFiltersWithQuickFilter(selectedQuickFilter),
 | 
			
		||||
                                );
 | 
			
		||||
                            }}
 | 
			
		||||
                        />
 | 
			
		||||
                        <>
 | 
			
		||||
                            {viewsEnabled && (
 | 
			
		||||
                                <ViewSelectContainer
 | 
			
		||||
                                    onClick={handleStopPropagation}
 | 
			
		||||
                                    onFocus={handleStopPropagation}
 | 
			
		||||
                                    onMouseDown={handleStopPropagation}
 | 
			
		||||
                                    onKeyUp={handleStopPropagation}
 | 
			
		||||
                                    onKeyDown={handleStopPropagation}
 | 
			
		||||
                                >
 | 
			
		||||
                                    <ViewSelect />
 | 
			
		||||
                                </ViewSelectContainer>
 | 
			
		||||
                            )}
 | 
			
		||||
                            <SearchIcon
 | 
			
		||||
                                onClick={() => {
 | 
			
		||||
                                    handleSearch(
 | 
			
		||||
                                        filterSearchQuery(searchQuery || ''),
 | 
			
		||||
                                        undefined,
 | 
			
		||||
                                        getFiltersWithQuickFilter(selectedQuickFilter),
 | 
			
		||||
                                    );
 | 
			
		||||
                                }}
 | 
			
		||||
                            />
 | 
			
		||||
                        </>
 | 
			
		||||
                    }
 | 
			
		||||
                />
 | 
			
		||||
            </StyledAutoComplete>
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,6 @@ import { ANTD_GRAY } from '../entity/shared/constants';
 | 
			
		||||
import { HeaderLinks } from '../shared/admin/HeaderLinks';
 | 
			
		||||
import { useAppConfig, useIsShowAcrylInfoEnabled } from '../useAppConfig';
 | 
			
		||||
import { DEFAULT_APP_CONFIG } from '../../appConfigContext';
 | 
			
		||||
import { ViewSelect } from '../entity/view/select/ViewSelect';
 | 
			
		||||
import DemoButton from '../entity/shared/components/styled/DemoButton';
 | 
			
		||||
 | 
			
		||||
const { Header } = Layout;
 | 
			
		||||
@ -49,10 +48,6 @@ const NavGroup = styled.div`
 | 
			
		||||
    min-width: 200px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const ViewSelectContainer = styled.span`
 | 
			
		||||
    margin-right: 14px;
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
    initialQuery: string;
 | 
			
		||||
    placeholderText: string;
 | 
			
		||||
@ -85,7 +80,7 @@ export const SearchHeader = ({
 | 
			
		||||
    const themeConfig = useTheme();
 | 
			
		||||
    const showAcrylInfo = useIsShowAcrylInfoEnabled();
 | 
			
		||||
    const appConfig = useAppConfig();
 | 
			
		||||
    const viewsEnabled = appConfig.config?.viewsConfig?.enabled;
 | 
			
		||||
    const viewsEnabled = appConfig.config?.viewsConfig?.enabled || false;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Header style={styles.header as any}>
 | 
			
		||||
@ -108,16 +103,12 @@ export const SearchHeader = ({
 | 
			
		||||
                    onQueryChange={onQueryChange}
 | 
			
		||||
                    entityRegistry={entityRegistry}
 | 
			
		||||
                    setIsSearchBarFocused={setIsSearchBarFocused}
 | 
			
		||||
                    viewsEnabled={viewsEnabled}
 | 
			
		||||
                    fixAutoComplete
 | 
			
		||||
                    showQuickFilters
 | 
			
		||||
                />
 | 
			
		||||
            </LogoSearchContainer>
 | 
			
		||||
            <NavGroup>
 | 
			
		||||
                {viewsEnabled && (
 | 
			
		||||
                    <ViewSelectContainer>
 | 
			
		||||
                        <ViewSelect />
 | 
			
		||||
                    </ViewSelectContainer>
 | 
			
		||||
                )}
 | 
			
		||||
                <HeaderLinks areLinksHidden={isSearchBarFocused} />
 | 
			
		||||
                <ManageAccount urn={authenticatedUserUrn} pictureLink={authenticatedUserPictureLink || ''} />
 | 
			
		||||
                {showAcrylInfo && <DemoButton />}
 | 
			
		||||
 | 
			
		||||
@ -1,86 +1,88 @@
 | 
			
		||||
function openViewEditDropDownAndClickId(data_id) {
 | 
			
		||||
  cy.openMultiSelect('view-select');
 | 
			
		||||
  cy.get('[data-testid="view-select-item"]').first().trigger('mouseover')
 | 
			
		||||
  cy.get('[data-testid="views-table-dropdown"]').first().trigger('mouseover');
 | 
			
		||||
  cy.openMultiSelect("view-select");
 | 
			
		||||
  cy.get('[data-testid="view-select-item"]').first().trigger("mouseover");
 | 
			
		||||
  cy.get('[data-testid="views-table-dropdown"]').first().trigger("mouseover");
 | 
			
		||||
  cy.clickOptionWithTestId(data_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
describe("view select", () => {
 | 
			
		||||
    it("click view select, create view, clear view, make defaults, clear view", () => {
 | 
			
		||||
        cy.login();
 | 
			
		||||
        let randomNumber = Math.floor(Math.random() * 100000);
 | 
			
		||||
        const viewName = `Test View ${randomNumber}`
 | 
			
		||||
        const newViewName = `New View Name ${randomNumber}`
 | 
			
		||||
 | 
			
		||||
        // Resize Observer Loop warning can be safely ignored - ref. https://github.com/cypress-io/cypress/issues/22113
 | 
			
		||||
        const resizeObserverLoopErrRe = "ResizeObserver loop limit exceeded";
 | 
			
		||||
        cy.on('uncaught:exception', (err) => {
 | 
			
		||||
          if (err.message.includes(resizeObserverLoopErrRe)) {
 | 
			
		||||
            return false;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        cy.goToStarSearchList();
 | 
			
		||||
 | 
			
		||||
        cy.log("Create a View from the select")
 | 
			
		||||
        cy.multiSelect('view-select', 'Create View');
 | 
			
		||||
        cy.waitTextVisible("Create new View")
 | 
			
		||||
 | 
			
		||||
        cy.enterTextInTestId("view-name-input", viewName);
 | 
			
		||||
 | 
			
		||||
        cy.log("Add Column Glossary Term Filter")
 | 
			
		||||
        cy.clickOptionWithText("Add Filter");
 | 
			
		||||
        cy.clickOptionWithText("Column Glossary Term");
 | 
			
		||||
 | 
			
		||||
        cy.waitTextVisible("Add Glossary Terms");
 | 
			
		||||
        cy.selectOptionInTagTermModal("CypressColumnInfoType");
 | 
			
		||||
 | 
			
		||||
        cy.clickOptionWithText("Save");
 | 
			
		||||
        cy.ensureTextNotPresent("Save");
 | 
			
		||||
        cy.waitTextVisible(viewName);
 | 
			
		||||
 | 
			
		||||
        cy.log("Ensure the View filter has been applied correctly.")
 | 
			
		||||
        cy.contains("SampleCypressHdfsDataset");
 | 
			
		||||
        cy.contains("cypress_logging_events");
 | 
			
		||||
        cy.contains("of 2 results");
 | 
			
		||||
 | 
			
		||||
        cy.log("Clear the selected view")
 | 
			
		||||
        cy.clearView(viewName);
 | 
			
		||||
        cy.ensureTextNotPresent("of 2 results");
 | 
			
		||||
 | 
			
		||||
        cy.log("Now edit the view")
 | 
			
		||||
        openViewEditDropDownAndClickId('view-dropdown-edit');
 | 
			
		||||
        cy.get(".ant-input-affix-wrapper > input[type='text']").first().clear().type(newViewName);
 | 
			
		||||
        cy.log("Update the actual filters by adding another filter")
 | 
			
		||||
        cy.contains("Add Filter").click();
 | 
			
		||||
        cy.clickOptionWithTestId('adv-search-add-filter-description');
 | 
			
		||||
        cy.enterTextInTestId('edit-text-input', "log event");
 | 
			
		||||
        cy.clickOptionWithTestId('edit-text-done-btn');
 | 
			
		||||
 | 
			
		||||
        cy.log("Save View")
 | 
			
		||||
        cy.clickOptionWithTestId("view-builder-save");
 | 
			
		||||
 | 
			
		||||
        cy.contains("cypress_logging_events");
 | 
			
		||||
        cy.contains("of 1 result");
 | 
			
		||||
        
 | 
			
		||||
        cy.log("Now set the View as the personal Default")
 | 
			
		||||
        cy.clearView(viewName);
 | 
			
		||||
        openViewEditDropDownAndClickId('view-dropdown-set-user-default');
 | 
			
		||||
        
 | 
			
		||||
        cy.contains("of 1 result");
 | 
			
		||||
        cy.clearView(newViewName);
 | 
			
		||||
        cy.log("Now unset as the personal default")
 | 
			
		||||
        openViewEditDropDownAndClickId('view-dropdown-remove-user-default');
 | 
			
		||||
        
 | 
			
		||||
        cy.log("Now delete the View")
 | 
			
		||||
        cy.clearView(newViewName);
 | 
			
		||||
        openViewEditDropDownAndClickId('view-dropdown-delete');
 | 
			
		||||
        cy.clickOptionWithText("Yes");
 | 
			
		||||
 | 
			
		||||
        cy.log("Ensure that the view was deleted.")
 | 
			
		||||
        cy.goToViewsSettings();
 | 
			
		||||
        cy.ensureTextNotPresent(newViewName);
 | 
			
		||||
        cy.ensureTextNotPresent("of 1 result");
 | 
			
		||||
  it("click view select, create view, clear view, make defaults, clear view", () => {
 | 
			
		||||
    cy.login();
 | 
			
		||||
    let randomNumber = Math.floor(Math.random() * 100000);
 | 
			
		||||
    const viewName = `Test View ${randomNumber}`;
 | 
			
		||||
    const newViewName = `New View Name ${randomNumber}`;
 | 
			
		||||
 | 
			
		||||
    // Resize Observer Loop warning can be safely ignored - ref. https://github.com/cypress-io/cypress/issues/22113
 | 
			
		||||
    const resizeObserverLoopErrRe = "ResizeObserver loop limit exceeded";
 | 
			
		||||
    cy.on("uncaught:exception", (err) => {
 | 
			
		||||
      if (err.message.includes(resizeObserverLoopErrRe)) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    cy.goToStarSearchList();
 | 
			
		||||
 | 
			
		||||
    cy.log("Create a View from the select");
 | 
			
		||||
    cy.multiSelect("view-select", "Create new view");
 | 
			
		||||
    cy.waitTextVisible("Create new view");
 | 
			
		||||
 | 
			
		||||
    cy.enterTextInTestId("view-name-input", viewName);
 | 
			
		||||
 | 
			
		||||
    cy.log("Add Column Glossary Term Filter");
 | 
			
		||||
    cy.clickOptionWithText("Add Filter");
 | 
			
		||||
    cy.clickOptionWithText("Column Glossary Term");
 | 
			
		||||
 | 
			
		||||
    cy.waitTextVisible("Add Glossary Terms");
 | 
			
		||||
    cy.selectOptionInTagTermModal("CypressColumnInfoType");
 | 
			
		||||
 | 
			
		||||
    cy.clickOptionWithText("Save");
 | 
			
		||||
    cy.ensureTextNotPresent("Save");
 | 
			
		||||
    cy.waitTextVisible(viewName);
 | 
			
		||||
 | 
			
		||||
    cy.log("Ensure the View filter has been applied correctly.");
 | 
			
		||||
    cy.contains("SampleCypressHdfsDataset");
 | 
			
		||||
    cy.contains("cypress_logging_events");
 | 
			
		||||
    cy.contains("of 2 results");
 | 
			
		||||
 | 
			
		||||
    cy.log("Clear the selected view");
 | 
			
		||||
    cy.clearView(viewName);
 | 
			
		||||
    cy.ensureTextNotPresent("of 2 results");
 | 
			
		||||
 | 
			
		||||
    cy.log("Now edit the view");
 | 
			
		||||
    openViewEditDropDownAndClickId("view-dropdown-edit");
 | 
			
		||||
    cy.get(".ant-input-affix-wrapper > input[type='text']")
 | 
			
		||||
      .first()
 | 
			
		||||
      .clear()
 | 
			
		||||
      .type(newViewName);
 | 
			
		||||
    cy.log("Update the actual filters by adding another filter");
 | 
			
		||||
    cy.contains("Add Filter").click();
 | 
			
		||||
    cy.clickOptionWithTestId("adv-search-add-filter-description");
 | 
			
		||||
    cy.enterTextInTestId("edit-text-input", "log event");
 | 
			
		||||
    cy.clickOptionWithTestId("edit-text-done-btn");
 | 
			
		||||
 | 
			
		||||
    cy.log("Save View");
 | 
			
		||||
    cy.clickOptionWithTestId("view-builder-save");
 | 
			
		||||
 | 
			
		||||
    cy.contains("cypress_logging_events");
 | 
			
		||||
    cy.contains("of 1 result");
 | 
			
		||||
 | 
			
		||||
    cy.log("Now set the View as the personal Default");
 | 
			
		||||
    cy.clearView(viewName);
 | 
			
		||||
    openViewEditDropDownAndClickId("view-dropdown-set-user-default");
 | 
			
		||||
 | 
			
		||||
    cy.contains("of 1 result");
 | 
			
		||||
    cy.clearView(newViewName);
 | 
			
		||||
    cy.log("Now unset as the personal default");
 | 
			
		||||
    openViewEditDropDownAndClickId("view-dropdown-remove-user-default");
 | 
			
		||||
 | 
			
		||||
    cy.log("Now delete the View");
 | 
			
		||||
    cy.clearView(newViewName);
 | 
			
		||||
    openViewEditDropDownAndClickId("view-dropdown-delete");
 | 
			
		||||
    cy.clickOptionWithText("Yes");
 | 
			
		||||
 | 
			
		||||
    cy.log("Ensure that the view was deleted.");
 | 
			
		||||
    cy.goToViewsSettings();
 | 
			
		||||
    cy.ensureTextNotPresent(newViewName);
 | 
			
		||||
    cy.ensureTextNotPresent("of 1 result");
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user