mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-02 11:39:12 +00:00
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:
parent
e494cdfb65
commit
7fb30b9548
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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',
|
||||
},
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -127,6 +127,9 @@
|
||||
.border-gray {
|
||||
border-color: @gray;
|
||||
}
|
||||
.border-light-gray {
|
||||
border-color: @light-border-color;
|
||||
}
|
||||
|
||||
.line-height-16 {
|
||||
line-height: 16px;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -95,6 +95,9 @@
|
||||
}
|
||||
|
||||
.gap-1 {
|
||||
gap: 4px;
|
||||
}
|
||||
.gap-4 {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -50,3 +50,4 @@
|
||||
@announcement-border: #ffc143;
|
||||
@test-parameter-bg-color: #e7ebf0;
|
||||
@group-title-color: #76746f;
|
||||
@light-border-color: #f0f0f0;
|
||||
|
||||
@ -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 [];
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
Loading…
x
Reference in New Issue
Block a user