feat(schema) Add search filter to Schema tab (#5845)

This commit is contained in:
Chris Collins 2022-09-09 14:28:53 -04:00 committed by GitHub
parent 5a0154f3fe
commit 3fb4fc35f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 389 additions and 29 deletions

View File

@ -74,6 +74,7 @@
"react": "^17.0.0",
"react-color": "^2.19.3",
"react-dom": "^17.0.0",
"react-highlighter": "^0.4.3",
"react-icons": "4.3.1",
"react-js-cron": "^2.1.0",
"react-router": "^5.2.0",

View File

@ -827,7 +827,7 @@ export const container2 = {
__typename: 'Container',
} as Container;
const glossaryTerm1 = {
export const glossaryTerm1 = {
urn: 'urn:li:glossaryTerm:1',
type: EntityType.GlossaryTerm,
name: 'Another glossary term',
@ -1077,7 +1077,7 @@ export const glossaryNode5 = {
__typename: 'GlossaryNode',
} as GlossaryNode;
const sampleTag = {
export const sampleTag = {
urn: 'urn:li:tag:abc-sample-tag',
name: 'abc-sample-tag',
description: 'sample tag description',

View File

@ -1,14 +1,16 @@
import React from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { Button, Popover, Select, Tooltip, Typography } from 'antd';
import { Button, Input, Popover, Select, Tooltip, Typography } from 'antd';
import { debounce } from 'lodash';
import {
AuditOutlined,
CaretDownOutlined,
FileTextOutlined,
QuestionCircleOutlined,
SearchOutlined,
TableOutlined,
} from '@ant-design/icons';
import styled from 'styled-components';
import styled from 'styled-components/macro';
import CustomPagination from './CustomPagination';
import TabToolbar from '../../../../shared/components/styled/TabToolbar';
import { SemanticVersionStruct } from '../../../../../../types.generated';
@ -16,11 +18,11 @@ import { toRelativeTimeString } from '../../../../../shared/time/timeUtils';
import { ANTD_GRAY, REDESIGN_COLORS } from '../../../../shared/constants';
import { navigateToVersionedDatasetUrl } from '../../../../shared/tabs/Dataset/Schema/utils/navigateToVersionedDatasetUrl';
import SchemaTimeStamps from './SchemaTimeStamps';
import getSchemaFilterFromQueryString from '../../../../shared/tabs/Dataset/Schema/utils/getSchemaFilterFromQueryString';
const SchemaHeaderContainer = styled.div`
display: flex;
justify-content: space-between;
padding-bottom: 16px;
width: 100%;
`;
@ -60,11 +62,12 @@ const ValueButton = styled(Button)<{ $highlighted: boolean }>`
const KeyValueButtonGroup = styled.div`
margin-right: 10px;
display: inline-block;
display: flex;
`;
// Below styles are for buttons on the right side of the Schema Header
const RightButtonsGroup = styled.div`
padding-left: 5px;
&&& {
display: flex;
justify-content: right;
@ -111,6 +114,14 @@ const StyledCaretDownOutlined = styled(CaretDownOutlined)`
}
`;
const StyledInput = styled(Input)`
border-radius: 70px;
max-width: 300px;
`;
const MAX_ROWS_BEFORE_DEBOUNCE = 50;
const HALF_SECOND_IN_MS = 500;
type Props = {
maxVersion?: number;
fetchVersions?: (version1: number, version2: number) => void;
@ -128,6 +139,8 @@ type Props = {
versionList: Array<SemanticVersionStruct>;
showSchemaAuditView: boolean;
setShowSchemaAuditView: any;
setFilterText: (text: string) => void;
numRows: number;
};
export default function SchemaHeader({
@ -147,6 +160,8 @@ export default function SchemaHeader({
versionList,
showSchemaAuditView,
setShowSchemaAuditView,
setFilterText,
numRows,
}: Props) {
const history = useHistory();
const location = useLocation();
@ -182,6 +197,12 @@ export default function SchemaHeader({
};
const schemaAuditToggleText = showSchemaAuditView ? 'Close column history' : 'View column history';
const debouncedSetFilterText = debounce(
(e: React.ChangeEvent<HTMLInputElement>) => setFilterText(e.target.value),
numRows > MAX_ROWS_BEFORE_DEBOUNCE ? HALF_SECOND_IN_MS : 0,
);
const schemaFilter = getSchemaFilterFromQueryString(location);
const docLink = 'https://datahubproject.io/docs/dev-guides/timeline/';
return (
<TabToolbar>
@ -219,6 +240,15 @@ export default function SchemaHeader({
) : (
<ShowVersionButton onClick={() => setEditMode?.(true)}>Back</ShowVersionButton>
))}
{!showRaw && (
<StyledInput
defaultValue={schemaFilter}
placeholder="Search in schema..."
onChange={debouncedSetFilterText}
allowClear
prefix={<SearchOutlined />}
/>
)}
</LeftButtonsGroup>
<RightButtonsGroup>
<SchemaTimeStamps lastObserved={lastObserved} lastUpdated={lastUpdated} />

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Typography } from 'antd';
import styled from 'styled-components';
import Highlight from 'react-highlighter';
import translateFieldPath from './translateFieldPath';
import { ExtendedSchemaFields } from './types';
import TypeLabel from '../../../../shared/tabs/Dataset/Schema/components/TypeLabel';
@ -32,6 +33,7 @@ const FieldPathText = styled(Typography.Text)`
export default function useSchemaTitleRenderer(
schemaMetadata: SchemaMetadata | undefined | null,
setSelectedFkFieldPath: (params: { fieldPath: string; constraint?: ForeignKeyConstraint | null } | null) => void,
filterText: string,
) {
const [highlightedConstraint, setHighlightedConstraint] = useState<string | null>(null);
@ -54,7 +56,9 @@ export default function useSchemaTitleRenderer(
return (
<>
<FieldPathContainer>
<FieldPathText>{pathToDisplay}</FieldPathText>
<FieldPathText>
<Highlight search={filterText}>{pathToDisplay}</Highlight>
</FieldPathText>
<TypeLabel type={record.type} nativeDataType={record.nativeDataType} />
{(schemaMetadata?.primaryKeys?.includes(fieldPath) || record.isPartOfKey) && <PrimaryKeyLabel />}
{schemaMetadata?.foreignKeys

View File

@ -1,6 +1,7 @@
import { Empty } from 'antd';
import React, { useEffect, useMemo, useState } from 'react';
import styled from 'styled-components';
import { useLocation } from 'react-router';
import { GetDatasetQuery } from '../../../../../../graphql/dataset.generated';
import { useGetSchemaBlameQuery, useGetSchemaVersionListQuery } from '../../../../../../graphql/schemaBlame.generated';
import SchemaEditableContext from '../../../../../shared/SchemaEditableContext';
@ -14,6 +15,10 @@ import { SchemaFieldBlame, SemanticVersionStruct } from '../../../../../../types
import SchemaTable from './SchemaTable';
import useGetSemanticVersionFromUrlParams from './utils/useGetSemanticVersionFromUrlParams';
import { useGetVersionedDatasetQuery } from '../../../../../../graphql/versionedDataset.generated';
import { useEntityRegistry } from '../../../../../useEntityRegistry';
import { filterSchemaRows } from './utils/filterSchemaRows';
import getSchemaFilterFromQueryString from './utils/getSchemaFilterFromQueryString';
import useUpdateSchemaFilterQueryString from './utils/updateSchemaFilterQueryString';
const NoSchema = styled(Empty)`
color: ${ANTD_GRAY[6]};
@ -26,6 +31,7 @@ const SchemaTableContainer = styled.div`
`;
export const SchemaTab = ({ properties }: { properties?: any }) => {
const { entityData } = useEntityData();
const entityRegistry = useEntityRegistry();
const baseEntity = useBaseEntity<GetDatasetQuery>();
const maybeEntityData = entityData || {};
let schemaMetadata: any = maybeEntityData?.schemaMetadata || undefined;
@ -33,6 +39,11 @@ export const SchemaTab = ({ properties }: { properties?: any }) => {
const datasetUrn: string = baseEntity?.dataset?.urn || '';
const usageStats = baseEntity?.dataset?.usageStats;
const [showRaw, setShowRaw] = useState(false);
const location = useLocation();
const schemaFilter = getSchemaFilterFromQueryString(location);
const [filterText, setFilterText] = useState(schemaFilter);
useUpdateSchemaFilterQueryString(filterText);
const hasRawSchema = useMemo(
() =>
schemaMetadata?.platformSchema?.__typename === 'TableSchema' &&
@ -113,9 +124,17 @@ export const SchemaTab = ({ properties }: { properties?: any }) => {
setShowKeySchema(true);
}
}, [hasValueSchema, hasKeySchema, setShowKeySchema]);
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
schemaMetadata?.fields,
editableSchemaMetadata,
filterText,
entityRegistry,
);
const rows = useMemo(() => {
return groupByFieldPath(schemaMetadata?.fields, { showKeySchema });
}, [schemaMetadata, showKeySchema]);
return groupByFieldPath(filteredRows, { showKeySchema });
}, [showKeySchema, filteredRows]);
const lastUpdated = getSchemaBlameData?.getSchemaBlame?.version?.semanticVersionTimestamp;
const lastObserved = versionedDatasetData.data?.versionedDataset?.schema?.lastObserved;
@ -139,6 +158,8 @@ export const SchemaTab = ({ properties }: { properties?: any }) => {
versionList={versionList}
showSchemaAuditView={showSchemaAuditView}
setShowSchemaAuditView={setShowSchemaAuditView}
setFilterText={setFilterText}
numRows={rows.length}
/>
<SchemaTableContainer>
{/* eslint-disable-next-line no-nested-ternary */}
@ -159,6 +180,8 @@ export const SchemaTab = ({ properties }: { properties?: any }) => {
usageStats={usageStats}
schemaFieldBlameList={schemaFieldBlameList}
showSchemaAuditView={showSchemaAuditView}
expandedRowsFromFilter={expandedRowsFromFilter as any}
filterText={filterText as any}
/>
</SchemaEditableContext.Provider>
</>

View File

@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { ColumnsType } from 'antd/es/table';
import styled from 'styled-components';
import {} from 'antd';
@ -45,6 +45,8 @@ export type Props = {
usageStats?: UsageQueryResult | null;
schemaFieldBlameList?: Array<SchemaFieldBlame> | null;
showSchemaAuditView: boolean;
expandedRowsFromFilter?: Set<string>;
filterText?: string;
};
export default function SchemaTable({
rows,
@ -54,6 +56,8 @@ export default function SchemaTable({
editMode = true,
schemaFieldBlameList,
showSchemaAuditView,
expandedRowsFromFilter = new Set(),
filterText = '',
}: Props): JSX.Element {
const hasUsageStats = useMemo(() => (usageStats?.aggregations?.fields?.length || 0) > 0, [usageStats]);
@ -63,15 +67,27 @@ export default function SchemaTable({
const descriptionRender = useDescriptionRenderer(editableSchemaMetadata);
const usageStatsRenderer = useUsageStatsRenderer(usageStats);
const tagRenderer = useTagsAndTermsRenderer(editableSchemaMetadata, tagHoveredIndex, setTagHoveredIndex, {
showTags: true,
showTerms: false,
});
const termRenderer = useTagsAndTermsRenderer(editableSchemaMetadata, tagHoveredIndex, setTagHoveredIndex, {
showTags: false,
showTerms: true,
});
const schemaTitleRenderer = useSchemaTitleRenderer(schemaMetadata, setSelectedFkFieldPath);
const tagRenderer = useTagsAndTermsRenderer(
editableSchemaMetadata,
tagHoveredIndex,
setTagHoveredIndex,
{
showTags: true,
showTerms: false,
},
filterText,
);
const termRenderer = useTagsAndTermsRenderer(
editableSchemaMetadata,
tagHoveredIndex,
setTagHoveredIndex,
{
showTags: false,
showTerms: true,
},
filterText,
);
const schemaTitleRenderer = useSchemaTitleRenderer(schemaMetadata, setSelectedFkFieldPath, filterText);
const schemaBlameRenderer = useSchemaBlameRenderer(schemaFieldBlameList);
const onTagTermCell = (record: SchemaField, rowIndex: number | undefined) => ({
@ -153,6 +169,17 @@ export default function SchemaTable({
allColumns = [...allColumns, blameColumn];
}
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
useEffect(() => {
setExpandedRows((previousRows) => {
const finalRowsSet = new Set();
expandedRowsFromFilter.forEach((row) => finalRowsSet.add(row));
previousRows.forEach((row) => finalRowsSet.add(row));
return finalRowsSet as Set<string>;
});
}, [expandedRowsFromFilter]);
return (
<FkContext.Provider value={selectedFkFieldPath}>
<TableContainer>
@ -169,9 +196,20 @@ export default function SchemaTable({
},
}}
expandable={{
expandedRowKeys: [...Array.from(expandedRows)],
defaultExpandAllRows: false,
expandRowByClick: false,
expandIcon: ExpandIcon,
onExpand: (expanded, record) => {
if (expanded) {
setExpandedRows((previousRows) => new Set(previousRows.add(record.fieldPath)));
} else {
setExpandedRows((previousRows) => {
previousRows.delete(record.fieldPath);
return new Set(previousRows);
});
}
},
indentSize: 0,
}}
pagination={false}

View File

@ -0,0 +1,122 @@
import { glossaryTerm1, sampleTag } from '../../../../../../../Mocks';
import { SchemaField } from '../../../../../../../types.generated';
import { getTestEntityRegistry } from '../../../../../../../utils/test-utils/TestPageContainer';
import { filterSchemaRows } from '../utils/filterSchemaRows';
describe('filterSchemaRows', () => {
const testEntityRegistry = getTestEntityRegistry();
const rows = [{ fieldPath: 'customer' }, { fieldPath: 'testing' }, { fieldPath: 'shipment' }] as SchemaField[];
it('should properly filter schema rows based on field name', () => {
const filterText = 'test';
const editableSchemaMetadata = { editableSchemaFieldInfo: [] };
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
rows,
editableSchemaMetadata,
filterText,
testEntityRegistry,
);
expect(filteredRows).toMatchObject([{ fieldPath: 'testing' }]);
expect(expandedRowsFromFilter).toMatchObject(new Set());
});
it('should properly filter schema rows based on field name regardless of capitalization', () => {
const editableSchemaMetadata = { editableSchemaFieldInfo: [] };
const filterText = 'TeSt';
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
rows,
editableSchemaMetadata,
filterText,
testEntityRegistry,
);
expect(filteredRows).toMatchObject([{ fieldPath: 'testing' }]);
expect(expandedRowsFromFilter).toMatchObject(new Set());
});
it('should properly filter schema rows based on tags', () => {
const editableSchemaMetadata = {
editableSchemaFieldInfo: [
{ fieldPath: 'customer', globalTags: { tags: [{ tag: sampleTag }] }, glossaryTerms: null },
],
};
const filterText = sampleTag.properties.name;
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
rows,
editableSchemaMetadata,
filterText,
testEntityRegistry,
);
expect(filteredRows).toMatchObject([{ fieldPath: 'customer' }]);
expect(expandedRowsFromFilter).toMatchObject(new Set());
});
it('should properly filter schema rows based on glossary terms', () => {
const editableSchemaMetadata = {
editableSchemaFieldInfo: [
{ fieldPath: 'shipment', globalTags: null, glossaryTerms: { terms: [{ term: glossaryTerm1 }] } },
],
};
const filterText = glossaryTerm1.properties?.name as string;
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
rows,
editableSchemaMetadata,
filterText,
testEntityRegistry,
);
expect(filteredRows).toMatchObject([{ fieldPath: 'shipment' }]);
expect(expandedRowsFromFilter).toMatchObject(new Set());
});
it('should properly filter and find children fields', () => {
const rowsWithChildren = [
{ fieldPath: 'customer' },
{ fieldPath: 'testing' },
{ fieldPath: 'customer.child1' },
{ fieldPath: 'customer.child2' },
] as SchemaField[];
const editableSchemaMetadata = { editableSchemaFieldInfo: [] };
const filterText = 'child';
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
rowsWithChildren,
editableSchemaMetadata,
filterText,
testEntityRegistry,
);
expect(filteredRows).toMatchObject([
{ fieldPath: 'customer' },
{ fieldPath: 'customer.child1' },
{ fieldPath: 'customer.child2' },
]);
expect(expandedRowsFromFilter).toMatchObject(new Set(['customer']));
});
it('should properly filter and find children fields multiple levels down', () => {
const rowsWithChildren = [
{ fieldPath: 'customer' },
{ fieldPath: 'testing' },
{ fieldPath: 'customer.child1' },
{ fieldPath: 'customer.child1.findMe' },
{ fieldPath: 'customer.child2' },
] as SchemaField[];
const editableSchemaMetadata = { editableSchemaFieldInfo: [] };
const filterText = 'find';
const { filteredRows, expandedRowsFromFilter } = filterSchemaRows(
rowsWithChildren,
editableSchemaMetadata,
filterText,
testEntityRegistry,
);
expect(filteredRows).toMatchObject([
{ fieldPath: 'customer' },
{ fieldPath: 'customer.child1' },
{ fieldPath: 'customer.child1.findMe' },
]);
expect(expandedRowsFromFilter).toMatchObject(new Set(['customer', 'customer.child1']));
});
});

View File

@ -0,0 +1,74 @@
import { EntityType, SchemaField } from '../../../../../../../types.generated';
import EntityRegistry from '../../../../../EntityRegistry';
// returns list of fieldPaths for fields that have Terms or Tags matching the filterText
function getFilteredFieldPathsByMetadata(editableSchemaMetadata: any, entityRegistry, filterText) {
return (
editableSchemaMetadata?.editableSchemaFieldInfo
.filter((fieldInfo) => {
return (
fieldInfo.globalTags?.tags.find((tagAssociation) =>
entityRegistry
.getDisplayName(EntityType.Tag, tagAssociation.tag)
.toLocaleLowerCase()
.includes(filterText),
) ||
fieldInfo.glossaryTerms?.terms.find((termAssociation) =>
entityRegistry
.getDisplayName(EntityType.GlossaryTerm, termAssociation.term)
.toLocaleLowerCase()
.includes(filterText),
)
);
})
.map((fieldInfo) => fieldInfo.fieldPath) || []
);
}
function shouldInclude(
fieldName: string,
fullFieldPath: string,
filterText: string,
filteredFieldPathsByMetadata: any,
) {
return fieldName.toLocaleLowerCase().includes(filterText) || filteredFieldPathsByMetadata.includes(fullFieldPath);
}
export function filterSchemaRows(
rows: SchemaField[],
editableSchemaMetadata: any,
filterText: string,
entityRegistry: EntityRegistry,
) {
if (!rows) return { filteredRows: [], expandedRowsFromFilter: new Set() };
if (!filterText) return { filteredRows: rows, expandedRowsFromFilter: new Set() };
const formattedFilterText = filterText.toLocaleLowerCase();
const filteredFieldPathsByMetadata = getFilteredFieldPathsByMetadata(
editableSchemaMetadata,
entityRegistry,
formattedFilterText,
);
const finalFieldPaths = new Set();
const expandedRowsFromFilter = new Set();
rows.forEach((row) => {
if (shouldInclude(row.fieldPath, row.fieldPath, formattedFilterText, filteredFieldPathsByMetadata)) {
finalFieldPaths.add(row.fieldPath);
}
const splitFieldPath = row.fieldPath.split('.');
const fieldName = splitFieldPath.slice(-1)[0];
if (shouldInclude(fieldName, row.fieldPath, formattedFilterText, filteredFieldPathsByMetadata)) {
// if we match specifically on this field (not just its parent), add and expand all parents
splitFieldPath.reduce((previous, current) => {
finalFieldPaths.add(previous);
expandedRowsFromFilter.add(previous);
return `${previous}.${current}`;
});
}
});
const filteredRows = rows.filter((row) => finalFieldPaths.has(row.fieldPath));
return { filteredRows, expandedRowsFromFilter };
}

View File

@ -0,0 +1,6 @@
import * as QueryString from 'query-string';
export default function getSchemaFilterFromQueryString(location: any) {
const params = QueryString.parse(location.search, { arrayFormat: 'comma' });
return decodeURIComponent(params.schemaFilter ? (params.schemaFilter as string) : '');
}

View File

@ -0,0 +1,21 @@
import { useHistory, useLocation } from 'react-router';
import * as QueryString from 'query-string';
import { useEffect } from 'react';
export default function useUpdateSchemaFilterQueryString(filterText: string) {
const location = useLocation();
const history = useHistory();
const parsedParams = QueryString.parse(location.search, { arrayFormat: 'comma' });
const newParams = {
...parsedParams,
schemaFilter: filterText,
};
const stringifiedParams = QueryString.stringify(newParams, { arrayFormat: 'comma' });
useEffect(() => {
history.push({
pathname: location.pathname,
search: stringifiedParams,
});
}, [filterText, history, location.pathname, stringifiedParams]);
}

View File

@ -9,6 +9,7 @@ export default function useTagsAndTermsRenderer(
tagHoveredIndex: string | undefined,
setTagHoveredIndex: (index: string | undefined) => void,
options: { showTags: boolean; showTerms: boolean },
filterText: string,
) {
const urn = useMutationUrn();
const refetch = useRefetch();
@ -33,6 +34,7 @@ export default function useTagsAndTermsRenderer(
entityUrn={urn}
entityType={EntityType.Dataset}
entitySubresource={record.fieldPath}
highlightText={filterText}
refetch={refetch}
/>
</div>

View File

@ -3,6 +3,7 @@ import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { BookOutlined, PlusOutlined } from '@ant-design/icons';
import Highlight from 'react-highlighter';
import { useEntityRegistry } from '../../useEntityRegistry';
import {
@ -38,6 +39,7 @@ type Props = {
entityUrn?: string;
entityType?: EntityType;
entitySubresource?: string;
highlightText?: string;
refetch?: () => Promise<any>;
};
@ -62,6 +64,8 @@ const TagText = styled.span`
margin: 0 7px 0 0;
`;
const highlightMatchStyle = { background: '#ffe58f', padding: '0' };
export default function TagTermGroup({
uneditableTags,
editableTags,
@ -78,6 +82,7 @@ export default function TagTermGroup({
entityUrn,
entityType,
entitySubresource,
highlightText,
refetch,
}: Props) {
const entityRegistry = useEntityRegistry();
@ -188,9 +193,11 @@ export default function TagTermGroup({
if (maxShow && renderedTags === maxShow + 1)
return (
<TagText>
{uneditableGlossaryTerms?.terms
? `+${uneditableGlossaryTerms?.terms?.length - maxShow}`
: null}
<Highlight matchStyle={highlightMatchStyle} search={highlightText}>
{uneditableGlossaryTerms?.terms
? `+${uneditableGlossaryTerms?.terms?.length - maxShow}`
: null}
</Highlight>
</TagText>
);
if (maxShow && renderedTags > maxShow) return null;
@ -224,7 +231,9 @@ export default function TagTermGroup({
}}
>
<BookOutlined style={{ marginRight: '3%' }} />
{entityRegistry.getDisplayName(EntityType.GlossaryTerm, term.term)}
<Highlight matchStyle={highlightMatchStyle} search={highlightText}>
{entityRegistry.getDisplayName(EntityType.GlossaryTerm, term.term)}
</Highlight>
</Tag>
</TermLink>
</HoverEntityTooltip>
@ -238,9 +247,10 @@ export default function TagTermGroup({
);
if (maxShow && renderedTags > maxShow) return null;
const displayName = entityRegistry.getDisplayName(EntityType.Tag, tag.tag);
return (
<HoverEntityTooltip entity={tag?.tag}>
<TagLink key={tag?.tag?.urn}>
<TagLink key={tag?.tag?.urn} data-testid={`tag-${displayName}`}>
<StyledTag
style={{ cursor: 'pointer' }}
onClick={() => showTagProfileDrawer(tag?.tag?.urn)}
@ -248,7 +258,9 @@ export default function TagTermGroup({
$color={tag?.tag?.properties?.colorHex}
closable={false}
>
{entityRegistry.getDisplayName(EntityType.Tag, tag.tag)}
<Highlight matchStyle={highlightMatchStyle} search={highlightText}>
{displayName}
</Highlight>
</StyledTag>
</TagLink>
</HoverEntityTooltip>
@ -258,9 +270,11 @@ export default function TagTermGroup({
{editableTags?.tags?.map((tag) => {
renderedTags += 1;
if (maxShow && renderedTags > maxShow) return null;
const displayName = entityRegistry.getDisplayName(EntityType.Tag, tag.tag);
return (
<HoverEntityTooltip entity={tag?.tag}>
<TagLink>
<TagLink data-testid={`tag-${displayName}`}>
<StyledTag
style={{ cursor: 'pointer' }}
onClick={() => showTagProfileDrawer(tag?.tag?.urn)}
@ -272,7 +286,9 @@ export default function TagTermGroup({
removeTag(tag);
}}
>
{tag?.tag?.name}
<Highlight matchStyle={highlightMatchStyle} search={highlightText}>
{displayName}
</Highlight>
</StyledTag>
</TagLink>
</HoverEntityTooltip>

View File

@ -4887,6 +4887,11 @@ bindings@^1.5.0:
dependencies:
file-uri-to-path "1.0.0"
blacklist@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/blacklist/-/blacklist-1.1.4.tgz#b2dd09d6177625b2caa69835a37b28995fa9a2f2"
integrity sha512-DWdfwimA1WQxVC69Vs1Fy525NbYwisMSCdYQmW9zyzOByz9OB/tQwrKZ3T3pbTkuFjnkJFlJuyiDjPiXL5kzew==
blob-util@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb"
@ -6049,6 +6054,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
create-react-class@^15.6.2:
version "15.7.0"
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e"
integrity sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==
dependencies:
loose-envify "^1.3.1"
object-assign "^4.1.1"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
@ -13963,6 +13976,16 @@ react-error-overlay@^6.0.9:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
react-highlighter@^0.4.3:
version "0.4.3"
resolved "https://registry.yarnpkg.com/react-highlighter/-/react-highlighter-0.4.3.tgz#e32c84d053259c30ca72c615aa759036d0d23048"
integrity sha512-dwItRaGRHBceuzZd5NXeroapdmZ2JCAWZ3AdwdthRlSkdtPCY18DWrd6mPmiMCfSB6lgVwwCPQl4unZzG5sXXw==
dependencies:
blacklist "^1.1.4"
create-react-class "^15.6.2"
escape-string-regexp "^1.0.5"
prop-types "^15.6.0"
react-icons@4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca"

View File

@ -50,7 +50,7 @@ describe("mutations", () => {
// verify dataset shows up in search now
cy.contains("of 1 result").click({ force: true });
cy.contains("cypress_logging_events").click({ force: true });
cy.contains("CypressTestAddTag").within(() =>
cy.get('[data-testid="tag-CypressTestAddTag"]').within(() =>
cy.get("span[aria-label=close]").click()
);
cy.contains("Yes").click();
@ -138,7 +138,7 @@ describe("mutations", () => {
// verify dataset shows up in search now
cy.contains("of 1 result").click();
cy.contains("cypress_logging_events").click();
cy.contains("CypressTestAddTag2").within(() =>
cy.get('[data-testid="tag-CypressTestAddTag2"]').within(() =>
cy
.get("span[aria-label=close]")
.trigger("mouseover", { force: true })