ui: facet filter and tags-container overflow bug fixes (#11119)

* Changed facet filter buckets render length. Only top 10 most used filters will be shown in the left panel.
Fixed the overflow issue of tags-container component for longer tag name

* Fixed overflowing issue while adding tags
Improved Tags component and use antd tag to show tags

* Reduced the gap

* Fixed tooltip placement issue with facet filters

* Fixed breadcrumb issue on dataInsights and ESReIndex pipeline logs page

* Fixed cypress tests

* Added delay for tooltip popup to avoid cypress flakyness

* Added delays to the tooltips and changed positions to avoid flakyness in cypress

* Fixed failing cypress tests

---------

Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
Aniket Katkar 2023-04-21 19:01:30 +05:30 committed by GitHub
parent e494cdfb65
commit 7fb30b9548
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 338 additions and 323 deletions

View File

@ -316,10 +316,7 @@ export const addTier = (tier) => {
.should('be.visible')
.click();
cy.get('[data-testid="tags"] > [data-testid="add-tag"]').should(
'contain',
'Tier1'
);
cy.get('[data-testid="tier-dropdown"]').should('contain', 'Tier1');
};
export const addTag = (tag) => {
@ -329,7 +326,9 @@ export const addTag = (tag) => {
SEARCH_ENTITY_TABLE.table_3.entity
);
cy.get('[data-testid="tags"] > [data-testid="add-tag"]')
cy.get(
'[data-testid="entity-tags"] [data-testid="tags"] [data-testid="add-tag"]'
)
.eq(0)
.should('be.visible')
.scrollIntoView()

View File

@ -550,7 +550,7 @@ export const addNewTagToEntity = (entityObj, term) => {
entityObj.entity
);
cy.wait(500);
cy.get('[data-testid="tags"] > [data-testid="add-tag"]')
cy.get('[data-testid="tags"] [data-testid="add-tag"]')
.eq(0)
.should('be.visible')
.scrollIntoView()

View File

@ -43,9 +43,7 @@ const removeTags = (tag, checkForParentEntity, isTable) => {
.should('be.visible')
.click();
cy.get('.ant-select-selection-item-remove > .anticon')
.should('be.visible')
.click();
cy.get('[data-testid="remove-tags"]').should('be.visible').click();
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
} else {
@ -65,7 +63,7 @@ const removeTags = (tag, checkForParentEntity, isTable) => {
.click();
}
cy.get(`[title="${tag}"] [data-testid="remove-tags"`)
cy.get(`[data-testid="selected-tag-${tag}"] [data-testid="remove-tags"`)
.should('be.visible')
.click();
@ -88,7 +86,7 @@ describe('Check if tags addition and removal flow working properly from tables',
);
cy.get(
'[data-testid="entity-tags"] [data-testid="tags-wrapper"] [data-testid="tag-container"] [data-testid="tags"] [data-testid="add-tag"] span'
'[data-testid="entity-tags"] [data-testid="tags-wrapper"] [data-testid="tag-container"] [data-testid="tags"] [data-testid="add-tag"]'
)
.should('be.visible')
.click();
@ -107,13 +105,13 @@ describe('Check if tags addition and removal flow working properly from tables',
if (entityDetails.entity === 'mlmodels') {
cy.get(
`[data-testid="feature-card-${entityDetails.fieldName}"] [data-testid="tag-container"] [data-testid="tags"] > [data-testid="add-tag"] span`
`[data-testid="feature-card-${entityDetails.fieldName}"] [data-testid="tag-container"] [data-testid="tags"] [data-testid="add-tag"]`
)
.should('be.visible')
.click();
} else {
cy.get(
`.ant-table-tbody [data-testid="tag-container"] [data-testid="add-tag"] span`
`.ant-table-tbody [data-testid="tag-container"] [data-testid="add-tag"]`
)
.eq(0)
.should('be.visible')

View File

@ -460,7 +460,7 @@ describe('Glossary page should work properly', () => {
.and('be.visible')
.click();
cy.get('.ant-select-selection-item-remove').should('be.visible').click();
cy.get('[data-testid="remove-tags"]').should('be.visible').click();
interceptURL('PATCH', '/api/v1/glossaries/*', 'updateGlossary');
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
verifyResponseStatusCode('@updateGlossary', 200);
@ -621,7 +621,7 @@ describe('Glossary page should work properly', () => {
visitEntityDetailsPage(entity.term, entity.serviceName, entity.entity);
// Add tag to breadcrumb
cy.get('[data-testid="tag-container"] [data-testid="tags"]')
cy.get('[data-testid="tag-container"] [data-testid="add-tag"]')
.eq(0)
.should('be.visible')
.click();
@ -660,8 +660,8 @@ describe('Glossary page should work properly', () => {
);
// Add non mutually exclusive tags
cy.get('[data-testid="tag-container"] [data-testid="tags"]')
.eq(0)
cy.get('[data-testid="entity-tags"] [data-testid="add-tag"]')
.scrollIntoView()
.should('be.visible')
.click();
@ -720,7 +720,10 @@ describe('Glossary page should work properly', () => {
).contains(term3);
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
verifyResponseStatusCode('@countTag', 200);
cy.get(`[data-testid="tag-${glossary1}.${term3}"]`)
cy.get(
`[data-row-key="comments"] [data-testid="tag-${glossary1}.${term3}"]`
)
.scrollIntoView()
.should('be.visible')
.contains(term3);
@ -775,7 +778,7 @@ describe('Glossary page should work properly', () => {
.should('be.visible')
.click();
// Remove all added tags from breadcrumb
cy.get('.ant-select-selection-item-remove')
cy.get('[data-testid="remove-tags"]')
.should('be.visible')
.click({ multiple: true });
@ -794,7 +797,9 @@ describe('Glossary page should work properly', () => {
.trigger('mouseover')
.click();
cy.get(`[title="${glossaryName}.${name}"] [data-testid="remove-tags"`)
cy.get(
`[data-testid="selected-tag-${glossaryName}.${name}"] [data-testid="remove-tags"`
)
.should('be.visible')
.click();

View File

@ -206,7 +206,7 @@ describe('Tags page should work', () => {
verifyResponseStatusCode('@databaseSchemasPage', 200);
verifyResponseStatusCode('@permissions', 200);
cy.get('[data-testid="tags"] > [data-testid="add-tag"]')
cy.get('[data-testid="tags"] [data-testid="add-tag"]')
.should('be.visible')
.click();
@ -228,18 +228,13 @@ describe('Tags page should work', () => {
cy.get('[data-testid="edit-button"]').should('exist').click();
// Remove all added tags
cy.get('.ant-select-selection-item-remove')
.eq(0)
.should('be.visible')
.click();
cy.get('[data-testid="remove-tags"]').eq(0).should('be.visible').click();
interceptURL('PATCH', '/api/v1/databaseSchemas/*', 'removeTags');
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
verifyResponseStatusCode('@removeTags', 200);
cy.get('[data-testid="tags"] > [data-testid="add-tag"]').should(
'be.visible'
);
cy.get('[data-testid="tags"] [data-testid="add-tag"]').should('be.visible');
});
it.skip('Add tag at DatabaseSchema level with task & suggestions', () => {
@ -317,18 +312,13 @@ describe('Tags page should work', () => {
cy.get('[data-testid="add-tag"]').should('exist').click();
// Remove all added tags
cy.get('.ant-select-selection-item-remove')
.eq(0)
.should('be.visible')
.click();
cy.get('[data-testid="remove-tags"]').eq(0).should('be.visible').click();
interceptURL('PATCH', '/api/v1/databaseSchemas/*', 'removeTags');
cy.get('[data-testid="saveAssociatedTag"]').should('be.visible').click();
verifyResponseStatusCode('@removeTags', 200);
cy.get('[data-testid="tags"] > [data-testid="add-tag"]').should(
'be.visible'
);
cy.get('[data-testid="tags"] [data-testid="add-tag"]').should('be.visible');
});
it('Check Usage of tag and it should redirect to explore page with tags filter', () => {

View File

@ -226,6 +226,7 @@ const MlModelFeaturesList: FC<MlModelFeaturesListProp> = ({
{`${t('label.tag-plural')}:`}
</Typography.Text>{' '}
<div
className="w-min-20"
data-testid="feature-tags-wrapper"
onClick={() => handleTagContainerClick(feature)}>
<TagsContainer

View File

@ -0,0 +1,40 @@
/*
* Copyright 2023 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import url('../../../styles/variables.less');
.tags-component-container {
.tag-container-style {
display: flex;
gap: 4px;
justify-content: center;
align-items: center;
font-size: 12px;
font-weight: 500;
cursor: pointer;
background-color: @white;
padding: 1px 8px;
margin: 1px 4px;
}
.label {
border: none;
background-color: transparent;
padding: 0px;
}
.outlined {
border: none;
background-color: transparent;
}
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2022 Collate.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const tagStyles = {
base: `relative tw-inline-flex text-xs font-medium
rounded-4 whitespace-nowrap h-6`,
contained: 'tw-bg-badge tw-mr-2 tw-my-0.5',
outlined: 'tw-bg-transparent tw-mr-2 tw-my-0.5',
label: 'tw-bg-transparent tw-border-none tw-text-grey-body',
border: 'tw-bg-white tw-border tw-items-center tw-mr-1 tw-mt-1',
text: {
base: 'tw-no-underline hover:tw-no-underline',
default: 'tw-px-2',
editable: 'tw-pl-2 tw-pr-1',
contained: 'tw-py-0.5 tw-px-2',
outlined: 'tw-py-0.5 tw-px-2',
border: 'tw-py-0.5 tw-px-2',
label: 'tw-px-1',
},
};

View File

@ -12,24 +12,21 @@
*/
import { CloseOutlined } from '@ant-design/icons';
import { Space, Tooltip } from 'antd';
import { Tag, Tooltip, Typography } from 'antd';
import classNames from 'classnames';
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
import { ROUTES } from 'constants/constants';
import { TagSource } from 'generated/type/tagLabel';
import { isEmpty } from 'lodash';
import React, { FunctionComponent, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { getTagDisplay } from 'utils/TagsUtils';
import { getTagDisplay, getTagTooltip } from 'utils/TagsUtils';
import { ReactComponent as IconPage } from '../../../assets/svg/ic-flat-doc.svg';
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
import { ReactComponent as IconTag } from '../../../assets/svg/tag-grey.svg';
import { TAG_START_WITH } from 'constants/Tag.constants';
import { TagProps } from './tags.interface';
import { tagStyles } from './tags.styles';
import './Tags.less';
const Tags: FunctionComponent<TagProps> = ({
className,
@ -41,13 +38,7 @@ const Tags: FunctionComponent<TagProps> = ({
removeTag,
isRemovable,
}: TagProps) => {
const { t } = useTranslation();
const history = useHistory();
const baseStyle = tagStyles.base;
const layoutStyles = tagStyles[type];
const textBaseStyle = tagStyles.text.base;
const textLayoutStyles = tagStyles.text[type] || tagStyles.text.default;
const textEditStyles = editable ? tagStyles.text.editable : '';
const getTagString = (tag: string) => {
return tag.startsWith('#') ? tag.slice(1) : tag;
@ -61,10 +52,18 @@ const Tags: FunctionComponent<TagProps> = ({
const startIcon = useMemo(() => {
switch (startWith) {
case TAG_START_WITH.PLUS:
return <PlusIcon height={16} name="plus" width={16} />;
return (
<PlusIcon
className="flex-shrink"
height={16}
name="plus"
width={16}
/>
);
case TAG_START_WITH.SOURCE_ICON:
return isGlossaryTag ? (
<IconPage
className="flex-shrink"
data-testid="glossary-icon"
height={12}
name="glossary-icon"
@ -72,6 +71,7 @@ const Tags: FunctionComponent<TagProps> = ({
/>
) : (
<IconTag
className="flex-shrink"
data-testid="tags-icon"
height={12}
name="tag-icon"
@ -89,82 +89,55 @@ const Tags: FunctionComponent<TagProps> = ({
: tag.tagFQN;
return (
<div
className={classNames(baseStyle, layoutStyles, className, 'tags-item')}
<Tag
className={classNames('tag-container-style', type, className)}
closable={editable && isRemovable}
closeIcon={<CloseOutlined className="tw-text-primary" />}
data-testid="tags"
icon={startIcon}
onClick={() => {
if (tag.source && startWith !== TAG_START_WITH.PLUS) {
tag.source === TagSource.Glossary
? history.push(`${ROUTES.GLOSSARY}/${tag.tagFQN}`)
: history.push(`${ROUTES.TAGS}/${tag.tagFQN.split('.')[0]}`);
}
}}
onClose={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault();
e.stopPropagation();
removeTag && removeTag(e, getTagString(tag.tagFQN));
}}>
<Space
align="center"
className={classNames(
textBaseStyle,
textLayoutStyles,
textEditStyles,
'd-flex items-center cursor-pointer'
)}
data-testid={editable ? `tag-${tag.tagFQN}` : 'add-tag'}
size={4}>
{startIcon}
<span
className={classNames(
'text-xs font-medium',
startWith === '+' && 'text-primary'
)}>
{getTagDisplay(tagName)}
{editable && isRemovable && (
<span
className="tw-py-0.5 tw-px-2 tw-rounded tw-cursor-pointer"
data-testid={`remove-${tag}-tag`}
onClick={(e: React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault();
e.stopPropagation();
removeTag && removeTag(e, getTagString(tag.tagFQN));
}}>
<CloseOutlined className="tw-text-primary" />
</span>
)}
</span>
</Space>
</div>
<Typography.Paragraph
className="m-0"
data-testid={
startWith === TAG_START_WITH.PLUS ? 'add-tag' : `tag-${tag.tagFQN}`
}
style={{
display: 'inline-block',
whiteSpace: 'normal',
wordBreak: 'break-all',
}}>
{getTagDisplay(tagName)}
</Typography.Paragraph>
</Tag>
);
}, [startIcon, tag, editable]);
return (
<>
<div className="tags-component-container">
{startWith === TAG_START_WITH.PLUS ? (
tagChip
) : (
<Tooltip
className="cursor-pointer"
mouseEnterDelay={1.5}
placement="bottomLeft"
title={
<div className="text-left p-xss">
<div className="m-b-xs">
<RichTextEditorPreviewer
enableSeeMoreVariant={false}
markdown={
!isEmpty(tag.description)
? `**${tag.tagFQN}**\n${tag.description}`
: t('label.no-entity', {
entity: t('label.description'),
})
}
textVariant="white"
/>
</div>
</div>
}
title={getTagTooltip(tag.tagFQN, tag.description)}
trigger="hover">
{tagChip}
</Tooltip>
)}
</>
</div>
);
};

View File

@ -12,13 +12,14 @@
*/
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Select, Space, Tooltip, Typography } from 'antd';
import { Button, Select, Space, Tag, Tooltip, Typography } from 'antd';
import { ReactComponent as IconEdit } from 'assets/svg/edit-new.svg';
import classNames from 'classnames';
import Tags from 'components/Tag/Tags/tags';
import { TAG_CONSTANT, TAG_START_WITH } from 'constants/Tag.constants';
import { isEmpty } from 'lodash';
import { EntityTags, TagOption } from 'Models';
import type { CustomTagProps } from 'rc-select/lib/BaseSelect';
import React, {
FunctionComponent,
useCallback,
@ -27,8 +28,8 @@ import React, {
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { getTagDisplay, getTagTooltip } from 'utils/TagsUtils';
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
import { TagSource } from '../../../generated/type/tagLabel';
import { withLoader } from '../../../hoc/withLoader';
import Fqn from '../../../utils/Fqn';
import { TagsContainerProps } from './tags-container.interface';
@ -117,7 +118,6 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
<Tags
editable
key={index}
showOnlyName={tag.source === TagSource.Glossary}
startWith={TAG_START_WITH.SOURCE_ICON}
tag={tag}
type="border"
@ -125,6 +125,45 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
);
};
const tagRenderer = (customTagProps: CustomTagProps) => {
const { label, onClose } = customTagProps;
const tagLabel = getTagDisplay(label as string);
const onPreventMouseDown = (event: React.MouseEvent<HTMLSpanElement>) => {
event.preventDefault();
event.stopPropagation();
};
return (
<Tag
closable
className="text-sm flex-center m-r-xss p-r-xss m-y-2 border-light-gray"
closeIcon={
<CloseOutlined data-testid="remove-tags" height={8} width={8} />
}
data-testid={`selected-tag-${tagLabel}`}
onClose={onClose}
onMouseDown={onPreventMouseDown}>
<Tooltip
className="cursor-pointer"
mouseEnterDelay={1.5}
placement="topLeft"
title={getTagTooltip(label as string)}
trigger="hover">
<Typography.Paragraph
className="m-0"
style={{
display: 'inline-block',
whiteSpace: 'normal',
wordBreak: 'break-all',
}}>
{tagLabel}
</Typography.Paragraph>
</Tooltip>
</Tag>
);
};
useEffect(() => {
setTags(selectedTags);
}, [selectedTags]);
@ -174,7 +213,7 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
<>
<Select
autoFocus
className={classNames('flex-grow', className)}
className={classNames('flex-grow w-max-95', className)}
data-testid="tag-selector"
defaultValue={selectedTagsInternal}
mode="multiple"
@ -185,12 +224,14 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
removeIcon={
<CloseOutlined data-testid="remove-tags" height={8} width={8} />
}
tagRender={tagRenderer}
onChange={handleTagSelection}>
{tagOptions.map(({ label, value, displayName }) => (
<Select.Option key={label} value={value}>
<Tooltip
destroyTooltipOnHide
placement="topLeft"
mouseEnterDelay={1.5}
placement="leftTop"
title={label}
trigger="hover">
{displayName}

View File

@ -11,8 +11,7 @@
* limitations under the License.
*/
import Tags from 'components/Tag/Tags/tags';
import { TAG_CONSTANT } from 'constants/Tag.constants';
import { Typography } from 'antd';
import React, { FunctionComponent, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
@ -69,14 +68,7 @@ const SearchOptions: FunctionComponent<SearchOptionsProp> = ({
to={getExplorePath({ search: searchText })}
onClick={() => setIsOpen(false)}>
{searchText}
<Tags
className="tw-text-grey-body"
tag={{
...TAG_CONSTANT,
tagFQN: t('label.in-open-metadata'),
}}
type="outlined"
/>
<Typography.Text>{t('label.in-open-metadata')}</Typography.Text>
</Link>
{options.map((option, index) => (
<span
@ -89,14 +81,7 @@ const SearchOptions: FunctionComponent<SearchOptionsProp> = ({
setIsOpen(false);
}}>
{searchText}
<Tags
className="tw-text-grey-body"
tag={{
...TAG_CONSTANT,
tagFQN: option,
}}
type="outlined"
/>
<Typography.Text>{option}</Typography.Text>
</span>
))}
</div>

View File

@ -12,7 +12,7 @@
*/
import { StarOutlined } from '@ant-design/icons';
import { Button, Dropdown, Popover, Space, Typography } from 'antd';
import { Button, Col, Dropdown, Popover, Row, Space, Typography } from 'antd';
import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { AxiosError } from 'axios';
import classNames from 'classnames';
@ -21,7 +21,7 @@ import VersionButton from 'components/VersionButton/VersionButton.component';
import { t } from 'i18next';
import { cloneDeep, isEmpty, isUndefined, toString } from 'lodash';
import { EntityTags, ExtraInfo, TagOption } from 'Models';
import React, { Fragment, useCallback, useMemo, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { getActiveAnnouncement } from 'rest/feedsAPI';
import { sortTagsCaseInsensitive } from 'utils/CommonUtils';
@ -233,30 +233,34 @@ const EntityPageInfo = ({
const getThreadElements = () => {
if (!isUndefined(entityFieldThreads)) {
return !isUndefined(tagThread) ? (
<Button
className="p-0 flex-center"
data-testid="tag-thread"
size="small"
type="text"
onClick={() => onThreadLinkSelect?.(tagThread.entityLink)}>
<Space align="center" className="w-full h-full" size={2}>
<IconComments height={16} name="comments" width={16} />
<span data-testid="tag-thread-count">{tagThread.count}</span>
</Space>
</Button>
<Col>
<Button
className="p-0 flex-center"
data-testid="tag-thread"
size="small"
type="text"
onClick={() => onThreadLinkSelect?.(tagThread.entityLink)}>
<Space align="center" className="w-full h-full" size={2}>
<IconComments height={16} name="comments" width={16} />
<span data-testid="tag-thread-count">{tagThread.count}</span>
</Space>
</Button>
</Col>
) : (
<Button
className="p-0 flex-center"
data-testid="start-tag-thread"
icon={<IconCommentPlus height={16} name="comments" width={16} />}
size="small"
type="text"
onClick={() =>
onThreadLinkSelect?.(
getEntityFeedLink(entityType, entityFqn, 'tags')
)
}
/>
<Col>
<Button
className="p-0 flex-center"
data-testid="start-tag-thread"
icon={<IconCommentPlus height={16} name="comments" width={16} />}
size="small"
type="text"
onClick={() =>
onThreadLinkSelect?.(
getEntityFeedLink(entityType, entityFqn, 'tags')
)
}
/>
</Col>
);
} else {
return null;
@ -271,44 +275,48 @@ const EntityPageInfo = ({
return onThreadLinkSelect &&
TASK_ENTITIES.includes(entityType as EntityType) ? (
<Button
className="p-0 flex-center"
data-testid="request-entity-tags"
size="small"
type="text"
onClick={hasTags ? handleUpdateTags : handleRequestTags}>
<Popover
destroyTooltipOnHide
content={text}
overlayClassName="ant-popover-request-description"
trigger="hover"
zIndex={9999}>
<IconRequest
className="anticon"
height={16}
name="request-tags"
width={16}
/>
</Popover>
</Button>
<Col>
<Button
className="p-0 flex-center"
data-testid="request-entity-tags"
size="small"
type="text"
onClick={hasTags ? handleUpdateTags : handleRequestTags}>
<Popover
destroyTooltipOnHide
content={text}
overlayClassName="ant-popover-request-description"
trigger="hover"
zIndex={9999}>
<IconRequest
className="anticon"
height={16}
name="request-tags"
width={16}
/>
</Popover>
</Button>
</Col>
) : null;
}, [tags]);
const getTaskElement = useCallback(() => {
return !isUndefined(tagTask) ? (
<Button
className="p-0 flex-center"
data-testid="tag-task"
size="small"
type="text"
onClick={() =>
onThreadLinkSelect?.(tagTask.entityLink, ThreadType.Task)
}>
<Space align="center" className="w-full h-full" size={2}>
<IconTaskColor height={16} name="comments" width={16} />
<span data-testid="tag-task-count">{tagTask.count}</span>
</Space>
</Button>
<Col>
<Button
className="p-0 flex-center"
data-testid="tag-task"
size="small"
type="text"
onClick={() =>
onThreadLinkSelect?.(tagTask.entityLink, ThreadType.Task)
}>
<Space align="center" className="w-full h-full" size={2}>
<IconTaskColor height={16} name="comments" width={16} />
<span data-testid="tag-task-count">{tagTask.count}</span>
</Space>
</Button>
</Col>
) : null;
}, [tagTask]);
@ -441,47 +449,47 @@ const EntityPageInfo = ({
</span>
))}
</Space>
<Space wrap align="center" data-testid="entity-tags" size={6}>
<Row align="middle" data-testid="entity-tags" gutter={8}>
{isTagEditable && !deleted && (
<Fragment>
<Space
align="center"
className="w-full h-full"
data-testid="tags-wrapper"
size={8}
onClick={() => {
// Fetch tags and terms only once
if (tagList.length === 0) {
fetchTags();
}
setIsEditable(true);
}}>
<TagsContainer
showEditTagButton
className="w-min-20"
dropDownHorzPosRight={false}
editable={isEditable}
isLoading={isTagLoading}
selectedTags={getSelectedTags()}
showAddTagButton={getSelectedTags().length === 0}
size="small"
tagList={tagList}
onCancel={() => {
handleTagSelection();
}}
onSelectionChange={(tags) => {
handleTagSelection(tags);
}}
/>
</Space>
<>
{getRequestTagsElements()}
{getTaskElement()}
{getThreadElements()}
</>
</Fragment>
<>
<Col>
<Space
align="center"
className="w-full h-full"
data-testid="tags-wrapper"
size={8}
onClick={() => {
// Fetch tags and terms only once
if (tagList.length === 0) {
fetchTags();
}
setIsEditable(true);
}}>
<TagsContainer
showEditTagButton
className="w-min-20"
dropDownHorzPosRight={false}
editable={isEditable}
isLoading={isTagLoading}
selectedTags={getSelectedTags()}
showAddTagButton={getSelectedTags().length === 0}
size="small"
tagList={tagList}
onCancel={() => {
handleTagSelection();
}}
onSelectionChange={(tags) => {
handleTagSelection(tags);
}}
/>
</Space>
</Col>
{getRequestTagsElements()}
{getTaskElement()}
{getThreadElements()}
</>
)}
</Space>
</Row>
</Space>
{activeAnnouncement && (
<AnnouncementCard

View File

@ -15,7 +15,7 @@ import { Button, Divider, Typography } from 'antd';
import classNames from 'classnames';
import { AggregationEntry } from 'interface/search.interface';
import { isEmpty, isNil } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { getSortedTierBucketList } from 'utils/EntityUtils';
@ -35,9 +35,6 @@ const FacetFilter: React.FC<FacetFilterProps> = ({
onClearFilter,
}) => {
const { t } = useTranslation();
const [aggregationsPageSize, setAggregationsPageSize] = useState(
Object.fromEntries(Object.keys(aggregations).map((k) => [k, 5]))
);
/**
* Merging aggregations with filters.
* The aim is to ensure that if there a filter on aggregationKey `k` with value `v`,
@ -100,18 +97,6 @@ const FacetFilter: React.FC<FacetFilterProps> = ({
.sort(([key1], [key2]) => compareAggregationKey(key1, key2));
}, [aggregations, filters]);
useEffect(() => {
if (!isEmpty(aggregations)) {
setAggregationsPageSize(
Object.fromEntries(
Object.keys(aggregations).map((k) =>
k in aggregationsPageSize ? [k, aggregationsPageSize[k]] : [k, 5]
)
)
);
}
}, [aggregations]);
return (
<div data-testid="face-filter">
<div className="sidebar-my-data-holder mt-2 mb-3 p-x-md">
@ -157,7 +142,9 @@ const FacetFilter: React.FC<FacetFilterProps> = ({
index,
{ length: aggregationsLength }
) => (
<div data-testid={`filter-heading-${aggregationKey}`} key={index}>
<div
data-testid={`filter-heading-${aggregationKey}`}
key={aggregationKey}>
<div className="d-flex justify-between flex-col p-x-md">
<Typography.Paragraph className="m-y-sm common-left-panel-card-heading">
{translateAggregationKeyToTitle(aggregationKey)}
@ -166,53 +153,20 @@ const FacetFilter: React.FC<FacetFilterProps> = ({
<div
className="sidebar-my-data-holder p-x-md"
data-testid="filter-container">
{aggregation.buckets
.slice(0, aggregationsPageSize[aggregationKey])
.map((bucket, index) => (
<FilterContainer
count={bucket.doc_count}
isSelected={
!isNil(filters) && aggregationKey in filters
? filters[aggregationKey].includes(bucket.key)
: false
}
key={index}
name={bucket.key}
type={aggregationKey}
onSelect={onSelectHandler}
/>
))}
<div className="m-y-sm">
{aggregationsPageSize[aggregationKey] <
aggregation.buckets.length && (
<p
className="link-text text-xs"
onClick={() =>
setAggregationsPageSize((prev) => ({
...prev,
[aggregationKey]: prev[aggregationKey] + 5,
}))
}>
{t('label.view-entity', {
entity: t('label.more-lowercase'),
})}
</p>
)}
{aggregationsPageSize[aggregationKey] > 5 && (
<p
className="link-text text-xs text-left"
onClick={() =>
setAggregationsPageSize((prev) => ({
...prev,
[aggregationKey]: Math.max(5, prev[aggregationKey] - 5),
}))
}>
{t('label.view-entity', {
entity: t('label.less-lowercase'),
})}
</p>
)}
</div>
{aggregation.buckets.slice(0, 10).map((bucket) => (
<FilterContainer
count={bucket.doc_count}
isSelected={
!isNil(filters) && aggregationKey in filters
? filters[aggregationKey].includes(bucket.key)
: false
}
key={bucket.key}
name={bucket.key}
type={aggregationKey}
onSelect={onSelectHandler}
/>
))}
</div>
{index !== aggregationsLength - 1 && <Divider className="m-0" />}
</div>

View File

@ -34,7 +34,7 @@ const FilterContainer: FunctionComponent<FilterContainerProp> = ({
: name;
return (
<Tooltip placement="top" title={formattedName} trigger="hover">
<Tooltip placement="topLeft" title={formattedName} trigger="hover">
{label || formattedName}
</Tooltip>
);

View File

@ -540,7 +540,7 @@ const AddAlertPage = () => {
style={{ margin: 0, marginBottom: '16px' }}
/>
)}
<div className="d-flex gap-1">
<div className="d-flex gap-4">
<div className="flex-1">
<Form.Item key={key} name={[name, 'name']}>
<Select

View File

@ -127,6 +127,9 @@
.border-gray {
border-color: @gray;
}
.border-light-gray {
border-color: @light-border-color;
}
.line-height-16 {
line-height: 16px;

View File

@ -86,8 +86,11 @@
.w-max-1080 {
max-width: 1080px;
}
.w-500 {
width: 500px;
.w-max-fit-content {
max-width: fit-content;
}
.w-max-95 {
max-width: 95%;
}
.w-300 {
width: 300px;

View File

@ -95,6 +95,9 @@
}
.gap-1 {
gap: 4px;
}
.gap-4 {
gap: 16px;
}

View File

@ -58,6 +58,10 @@
margin-top: 0 !important;
margin-bottom: 0 !important;
}
.m-y-2 {
margin-top: 2px;
margin-bottom: 2px;
}
.m-y-xs {
margin-top: @margin-xs;
margin-bottom: @margin-xs;

View File

@ -50,3 +50,4 @@
@announcement-border: #ffc143;
@test-parameter-bg-color: #e7ebf0;
@group-title-color: #76746f;
@light-border-color: #f0f0f0;

View File

@ -11,8 +11,10 @@
* limitations under the License.
*/
import { OPEN_METADATA } from 'constants/service-guide.constant';
import { isUndefined, startCase } from 'lodash';
import { IngestionPipeline } from '../generated/entity/services/ingestionPipelines/ingestionPipeline';
import { getSettingsPathFromPipelineType } from './IngestionUtils';
import { getLogEntityPath } from './RouterUtils';
/**
@ -30,6 +32,20 @@ export const getLogBreadCrumbs = (
ingestionName: string,
ingestionDetails: IngestionPipeline | undefined
) => {
if (ingestionName.split('.')[0] === OPEN_METADATA && ingestionDetails) {
return [
{
name: startCase(ingestionDetails.pipelineType),
url: getSettingsPathFromPipelineType(ingestionDetails.pipelineType),
activeTitle: true,
},
{
name: startCase(ingestionName.split('.')[1]),
url: '',
activeTitle: true,
},
];
}
if (isUndefined(ingestionDetails)) {
return [];
}

View File

@ -13,16 +13,18 @@
import { RuleObject } from 'antd/lib/form';
import { AxiosError } from 'axios';
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
import { delimiterRegex } from 'constants/regex.constants';
import i18next from 'i18next';
import { isEmpty, isUndefined, toLower } from 'lodash';
import { Bucket, EntityTags, TagOption } from 'Models';
import React from 'react';
import {
getAllClassifications,
getClassificationByName,
getTags,
} from 'rest/tagAPI';
import { TAG_VIEW_CAP } from '../constants/constants';
import { SettledStatus } from '../enums/axios.enum';
import { Classification } from '../generated/entity/classification/classification';
import { Tag } from '../generated/entity/classification/tag';
@ -179,7 +181,15 @@ export const getTagsWithLabel = (tags: Array<Bucket>) => {
// Will return tag with ellipses if it exceeds the limit
export const getTagDisplay = (tag: string) => {
return tag.length > TAG_VIEW_CAP ? `${tag.slice(0, TAG_VIEW_CAP)}...` : tag;
const tagLevelsArray = tag.split(FQN_SEPARATOR_CHAR);
if (tagLevelsArray.length > 3) {
return `${tagLevelsArray[0]}...${tagLevelsArray
.slice(-2)
.join(FQN_SEPARATOR_CHAR)}`;
}
return tag;
};
export const fetchTagsAndGlossaryTerms = async () => {
@ -250,3 +260,15 @@ export const tagsNameValidator =
return Promise.resolve();
};
export const getTagTooltip = (fqn: string, description?: string) => (
<div className="text-left p-xss">
<div className="m-b-xs">
<RichTextEditorPreviewer
enableSeeMoreVariant={false}
markdown={`**${fqn}**\n${description ?? ''}`}
textVariant="white"
/>
</div>
</div>
);